diff --git a/.github/versions.sh b/.github/versions.sh index 439fc660f8607..a96fbbba552b9 100644 --- a/.github/versions.sh +++ b/.github/versions.sh @@ -2,7 +2,7 @@ PHP_VERSION=8.2 COMPOSER_VERSION=2.8.3 NODE_VERSION=22.9.0 -PNPM_VERSION=9.3.0 +PNPM_VERSION=9.15.0 # Other useful version numbers. MIN_PHP_VERSION=7.2 diff --git a/.github/workflows/gardening.yml b/.github/workflows/gardening.yml index d87dd2fdb9e71..a32fee68ea632 100644 --- a/.github/workflows/gardening.yml +++ b/.github/workflows/gardening.yml @@ -37,7 +37,7 @@ jobs: composer build-development working-directory: ./projects/github-actions/repo-gardening - - name: Checkout the PR + - name: Check out the PR if: github.event_name == 'pull_request_target' && github.event.pull_request.state != 'closed' uses: actions/checkout@v4 with: diff --git a/.husky/post-checkout b/.husky/post-checkout index 9446120543228..2c34b3c5d7416 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - ./tools/js-tools/git-hooks/post-merge-checkout-hook.sh "$1" diff --git a/.husky/post-merge b/.husky/post-merge index 7399d9143f1bb..1ab789ff25e76 100755 --- a/.husky/post-merge +++ b/.husky/post-merge @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - ./tools/js-tools/git-hooks/post-merge-checkout-hook.sh ORIG_HEAD diff --git a/.husky/pre-commit b/.husky/pre-commit index 3f41ae0243919..7530bd93a2b36 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - node tools/js-tools/git-hooks/pre-commit-hook.js diff --git a/.husky/pre-push b/.husky/pre-push index 300e69f5b8f78..8b85d51a7d8ae 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,3 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - if test -c /dev/tty && sh -c ': < /dev/tty' >/dev/null 2>/dev/null; then exec < /dev/tty fi diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index 424998f096eea..8483ae624e8fd 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -223,6 +223,16 @@ function afterAllResolved( lockfile ) { return lockfile; } + for ( const [ k, v ] of Object.entries( lockfile.packages ) ) { + // Forbid installing webpack without webpack-cli. It results in lots of spurious lockfile changes. + // https://github.com/pnpm/pnpm/issues/3935 + if ( k.startsWith( 'webpack@' ) && ! v.optionalDependencies?.[ 'webpack-cli' ] ) { + throw new Error( + "Something you've done is trying to add a dependency on webpack without webpack-cli.\nThis is not allowed, as it tends to result in pnpm lockfile flip-flopping.\nSee https://github.com/pnpm/pnpm/issues/3935 for the upstream bug report.\n" + ); + } + } + return lockfile; } diff --git a/docs/git-workflow.md b/docs/git-workflow.md index c2ec0b5c6aa1c..ccb35850f08b8 100644 --- a/docs/git-workflow.md +++ b/docs/git-workflow.md @@ -80,7 +80,7 @@ If you're working directly with Jetpack trunk and need to update an external con This method assumes you are using the `gh` shorthand from the [Github CLI](https://cli.github.com/): ```sh -# Use the Github CLI to checkout the PR with the PR number - for example gh pr checkout 12345. +# Use the Github CLI to check out the PR with the PR number - for example gh pr checkout 12345. gh pr checkout xxxxx # Run merge-base to check where that branch differed from trunk - example git merge-base update/broken-jetpack-feature trunk. diff --git a/package.json b/package.json index c48245d935c8a..954da13145901 100644 --- a/package.json +++ b/package.json @@ -23,21 +23,21 @@ "php:autofix": "composer phpcs:fix", "php:compatibility": "composer phpcs:compatibility", "php:lint": "composer phpcs:lint", - "prepare": "husky install", + "prepare": "husky", "reformat-files": "prettier --ignore-path .eslintignore --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,svelte,json,json5}\"", "version-packages": "bash ./tools/version-packages.sh" }, "devDependencies": { "eslint": "9.16.0", - "husky": "8.0.3", + "husky": "9.1.7", "jetpack-cli": "workspace:*", "jetpack-js-tools": "workspace:*" }, "engines": { "node": "^22.9.0", - "pnpm": "^9.3.0 <9.12.0" + "pnpm": "^9.15.0" }, - "packageManager": "pnpm@9.3.0", + "packageManager": "pnpm@9.15.0", "pnpm": { "patchedDependencies": { "@wordpress/dataviews@4.10.0": ".pnpm-patches/@wordpress__dataviews@4.10.0.patch" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eba1d19846d96..480cdf0529f9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,7 +4,7 @@ settings: autoInstallPeers: false excludeLinksFromLockfile: false -pnpmfileChecksum: kmseazxfbymwdp5ie53bnph5mq +pnpmfileChecksum: elvoqfz3hkxf3joxybwjpabdjm patchedDependencies: '@wordpress/dataviews@4.10.0': @@ -19,8 +19,8 @@ importers: specifier: 9.16.0 version: 9.16.0 husky: - specifier: 8.0.3 - version: 8.0.3 + specifier: 9.1.7 + version: 9.1.7 jetpack-cli: specifier: workspace:* version: link:tools/cli @@ -645,11 +645,11 @@ importers: specifier: 1.0.1 version: 1.0.1 playwright: - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 playwright-core: specifier: ^1.45.1 - version: 1.49.0 + version: 1.49.1 process: specifier: 0.11.10 version: 0.11.10 @@ -673,7 +673,7 @@ importers: version: 4.9.1(webpack@5.94.0) webpack-dev-middleware: specifier: 5.3.4 - version: 5.3.4(webpack@5.94.0(webpack-cli@4.9.1)) + version: 5.3.4(webpack@5.94.0) projects/js-packages/eslint-changed: dependencies: @@ -752,7 +752,7 @@ importers: devDependencies: '@wordpress/dependency-extraction-webpack-plugin': specifier: 6.14.0 - version: 6.14.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.14.0(webpack@5.94.0) '@wordpress/i18n': specifier: 5.14.0 version: 5.14.0 @@ -1440,8 +1440,8 @@ importers: specifier: 7.26.0 version: 7.26.0 '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 '@storybook/addon-a11y': specifier: 8.3.5 version: 8.3.5(storybook@8.3.5) @@ -1456,7 +1456,7 @@ importers: version: 8.3.5(storybook@8.3.5) '@storybook/addon-webpack5-compiler-babel': specifier: ^3.0.3 - version: 3.0.3(webpack@5.94.0(webpack-cli@4.9.1)) + version: 3.0.3(webpack@5.94.0) '@storybook/blocks': specifier: 8.3.5 version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5) @@ -1471,7 +1471,7 @@ importers: version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4) '@storybook/react-webpack5': specifier: 8.3.5 - version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0)) + version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1) '@storybook/source-loader': specifier: 8.3.5 version: 8.3.5(storybook@8.3.5) @@ -1507,16 +1507,16 @@ importers: version: 2.9.2 babel-loader: specifier: 9.1.2 - version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0) babel-plugin-inline-json-import: specifier: 0.3.2 version: 0.3.2 css-loader: specifier: 6.5.1 - version: 6.5.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.5.1(webpack@5.94.0) esbuild-loader: specifier: 3.0.1 - version: 3.0.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 3.0.1(webpack@5.94.0) jest: specifier: 29.7.0 version: 29.7.0 @@ -1528,7 +1528,7 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) react: specifier: 18.3.1 version: 18.3.1 @@ -1546,7 +1546,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -1555,7 +1555,7 @@ importers: version: 5.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) style-loader: specifier: 2.0.0 - version: 2.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 2.0.0(webpack@5.94.0) ts-dedent: specifier: 2.2.0 version: 2.2.0 @@ -1642,7 +1642,7 @@ importers: version: link:../i18n-loader-webpack-plugin '@automattic/webpack-rtl-plugin': specifier: 6.0.0 - version: 6.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.0.0(webpack@5.94.0) '@babel/compat-data': specifier: 7.26.2 version: 7.26.2 @@ -1663,16 +1663,16 @@ importers: version: 7.26.0(@babel/core@7.26.0) '@cerner/duplicate-package-checker-webpack-plugin': specifier: 2.3.0 - version: 2.3.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 2.3.0(webpack@5.94.0) '@wordpress/browserslist-config': specifier: 6.14.0 version: 6.14.0 '@wordpress/dependency-extraction-webpack-plugin': specifier: 6.14.0 - version: 6.14.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.14.0(webpack@5.94.0) babel-loader: specifier: 9.1.2 - version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0) babel-plugin-polyfill-corejs3: specifier: 0.10.6 version: 0.10.6(@babel/core@7.26.0) @@ -1684,22 +1684,22 @@ importers: version: 3.38.1 css-loader: specifier: 6.5.1 - version: 6.5.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.5.1(webpack@5.94.0) css-minimizer-webpack-plugin: specifier: 5.0.1 - version: 5.0.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 5.0.1(webpack@5.94.0) fork-ts-checker-webpack-plugin: specifier: 9.0.2 - version: 9.0.2(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 9.0.2(typescript@5.0.4)(webpack@5.94.0) mini-css-extract-plugin: specifier: 2.9.1 - version: 2.9.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 2.9.1(webpack@5.94.0) terser-webpack-plugin: specifier: 5.3.3 - version: 5.3.3(webpack@5.94.0(webpack-cli@4.9.1)) + version: 5.3.3(webpack@5.94.0) thread-loader: specifier: 3.0.4 - version: 3.0.4(webpack@5.94.0(webpack-cli@4.9.1)) + version: 3.0.4(webpack@5.94.0) devDependencies: '@babel/core': specifier: 7.26.0 @@ -1845,7 +1845,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -1918,7 +1918,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -1945,7 +1945,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -1983,13 +1983,13 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2041,7 +2041,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2141,7 +2141,7 @@ importers: version: 2.1.1 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) email-validator: specifier: 2.0.4 version: 2.0.4 @@ -2245,10 +2245,10 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -2379,8 +2379,8 @@ importers: specifier: 7.25.9 version: 7.25.9(@babel/core@7.26.0) '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 '@types/node': specifier: ^20.4.2 version: 20.17.9 @@ -2401,7 +2401,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: ^5.0.4 version: 5.7.2 @@ -2425,7 +2425,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2471,13 +2471,13 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2616,7 +2616,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -2642,7 +2642,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) tslib: specifier: 2.5.0 version: 2.5.0 @@ -2819,7 +2819,7 @@ importers: version: 6.14.0 '@wordpress/dependency-extraction-webpack-plugin': specifier: 6.14.0 - version: 6.14.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.14.0(webpack@5.94.0) autoprefixer: specifier: 10.4.14 version: 10.4.14(postcss@8.4.47) @@ -2846,13 +2846,13 @@ importers: version: 12.1.7(postcss@8.4.47) postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) size-limit: specifier: 11.1.6 version: 11.1.6(@size-limit/preset-app@11.1.6) @@ -3013,7 +3013,7 @@ importers: version: 7.6.0 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) jest: specifier: 29.7.0 version: 29.7.0 @@ -3028,7 +3028,7 @@ importers: version: 12.1.7(postcss@8.4.47) postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) require-from-string: specifier: 2.0.2 version: 2.0.2 @@ -3037,7 +3037,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -3191,7 +3191,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3257,7 +3257,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3345,7 +3345,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3356,8 +3356,8 @@ importers: projects/plugins/automattic-for-agencies-client/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -3402,7 +3402,7 @@ importers: version: 2.1.1 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) history: specifier: 5.3.0 version: 5.3.0 @@ -3493,7 +3493,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -3513,8 +3513,8 @@ importers: projects/plugins/boost/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -3593,7 +3593,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3604,8 +3604,8 @@ importers: projects/plugins/classic-theme-helper-plugin/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -3702,7 +3702,7 @@ importers: version: 29.3.1(@babel/core@7.26.0) css-loader: specifier: 6.5.1 - version: 6.5.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.5.1(webpack@5.94.0) glob: specifier: 10.4.1 version: 10.4.1 @@ -3717,7 +3717,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -3919,7 +3919,7 @@ importers: version: 1.0.1 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) crypto-js: specifier: 4.2.0 version: 4.2.0 @@ -4134,13 +4134,13 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) regenerator-runtime: specifier: 0.13.9 version: 0.13.9 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -4148,8 +4148,8 @@ importers: projects/plugins/jetpack/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4257,7 +4257,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -4273,8 +4273,8 @@ importers: projects/plugins/search/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4395,13 +4395,13 @@ importers: version: 12.1.7(postcss@8.4.47) postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -4412,8 +4412,8 @@ importers: projects/plugins/social/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4492,7 +4492,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -4503,8 +4503,8 @@ importers: projects/plugins/starter-plugin/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4597,7 +4597,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -4608,8 +4608,8 @@ importers: projects/plugins/videopress/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4726,8 +4726,8 @@ importers: tools/e2e-commons: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 '@slack/web-api': specifier: 7.3.2 version: 7.3.2 @@ -4805,7 +4805,7 @@ importers: version: 6.5.0 '@wordpress/eslint-plugin': specifier: 22.0.0 - version: 22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0))(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3) + version: 22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0)(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3) '@wordpress/jest-console': specifier: 8.14.0 version: 8.14.0(jest@29.7.0) @@ -6509,8 +6509,8 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.45.1': - resolution: {integrity: sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==} + '@playwright/test@1.48.2': + resolution: {integrity: sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==} engines: {node: '>=18'} hasBin: true @@ -10768,9 +10768,9 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - husky@8.0.3: - resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} - engines: {node: '>=14'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} hasBin: true iconv-lite@0.4.24: @@ -12372,18 +12372,18 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} - playwright-core@1.45.1: - resolution: {integrity: sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==} + playwright-core@1.48.2: + resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==} engines: {node: '>=18'} hasBin: true - playwright-core@1.49.0: - resolution: {integrity: sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==} + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} engines: {node: '>=18'} hasBin: true - playwright@1.45.1: - resolution: {integrity: sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==} + playwright@1.48.2: + resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==} engines: {node: '>=18'} hasBin: true @@ -14840,7 +14840,7 @@ snapshots: '@automattic/viewport@1.0.0': {} - '@automattic/webpack-rtl-plugin@6.0.0(webpack@5.94.0(webpack-cli@4.9.1))': + '@automattic/webpack-rtl-plugin@6.0.0(webpack@5.94.0)': dependencies: rtlcss: 3.5.0 webpack: 5.94.0(webpack-cli@4.9.1) @@ -15660,7 +15660,7 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@cerner/duplicate-package-checker-webpack-plugin@2.3.0(webpack@5.94.0(webpack-cli@4.9.1))': + '@cerner/duplicate-package-checker-webpack-plugin@2.3.0(webpack@5.94.0)': dependencies: chalk: 4.1.2 find-root: 1.1.0 @@ -16487,9 +16487,9 @@ snapshots: '@pkgr/core@0.1.1': {} - '@playwright/test@1.45.1': + '@playwright/test@1.48.2': dependencies: - playwright: 1.45.1 + playwright: 1.48.2 '@popperjs/core@2.11.8': {} @@ -17185,21 +17185,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@size-limit/file@11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6))': + '@size-limit/file@11.1.6(size-limit@11.1.6)': dependencies: size-limit: 11.1.6(@size-limit/preset-app@11.1.6) '@size-limit/preset-app@11.1.6(size-limit@11.1.6)': dependencies: - '@size-limit/file': 11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6)) - '@size-limit/time': 11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6)) + '@size-limit/file': 11.1.6(size-limit@11.1.6) + '@size-limit/time': 11.1.6(size-limit@11.1.6) size-limit: 11.1.6(@size-limit/preset-app@11.1.6) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@size-limit/time@11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6))': + '@size-limit/time@11.1.6(size-limit@11.1.6)': dependencies: estimo: 3.0.3 size-limit: 11.1.6(@size-limit/preset-app@11.1.6) @@ -17341,10 +17341,10 @@ snapshots: memoizerific: 1.11.3 storybook: 8.3.5 - '@storybook/addon-webpack5-compiler-babel@3.0.3(webpack@5.94.0(webpack-cli@4.9.1))': + '@storybook/addon-webpack5-compiler-babel@3.0.3(webpack@5.94.0)': dependencies: '@babel/core': 7.26.0 - babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)) + babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.94.0) transitivePeerDependencies: - supports-color - webpack @@ -17380,7 +17380,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-webpack5@8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0))': + '@storybook/builder-webpack5@8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1)': dependencies: '@storybook/core-webpack': 8.3.5(storybook@8.3.5) '@types/node': 22.10.1 @@ -17389,25 +17389,25 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.94.0(webpack-cli@4.9.1)) + css-loader: 6.11.0(webpack@5.94.0) es-module-lexer: 1.5.4 express: 4.21.2 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.0.4)(webpack@5.94.0) fs-extra: 11.2.0 - html-webpack-plugin: 5.6.3(webpack@5.94.0(webpack-cli@4.9.1)) + html-webpack-plugin: 5.6.3(webpack@5.94.0) magic-string: 0.30.14 path-browserify: 1.0.1 process: 0.11.10 semver: 7.5.2 storybook: 8.3.5 - style-loader: 3.3.4(webpack@5.94.0(webpack-cli@4.9.1)) - terser-webpack-plugin: 5.3.3(webpack@5.94.0(webpack-cli@4.9.1)) + style-loader: 3.3.4(webpack@5.94.0) + terser-webpack-plugin: 5.3.3(webpack@5.94.0) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 webpack: 5.94.0(webpack-cli@4.9.1) - webpack-dev-middleware: 6.1.3(webpack@5.94.0(webpack-cli@4.9.1)) + webpack-dev-middleware: 6.1.3(webpack@5.94.0) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -17520,11 +17520,11 @@ snapshots: dependencies: storybook: 8.4.6 - '@storybook/preset-react-webpack@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0))': + '@storybook/preset-react-webpack@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1)': dependencies: '@storybook/core-webpack': 8.3.5(storybook@8.3.5) '@storybook/react': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0) '@types/node': 22.10.1 '@types/semver': 7.5.8 find-up: 5.0.0 @@ -17560,7 +17560,7 @@ snapshots: dependencies: storybook: 8.4.6 - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0)': dependencies: debug: 4.3.4 endent: 2.1.0 @@ -17586,10 +17586,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.4.6 - '@storybook/react-webpack5@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0))': + '@storybook/react-webpack5@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1)': dependencies: - '@storybook/builder-webpack5': 8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0)) - '@storybook/preset-react-webpack': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0)) + '@storybook/builder-webpack5': 8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1) + '@storybook/preset-react-webpack': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1) '@storybook/react': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4) '@types/node': 22.10.1 react: 18.3.1 @@ -17679,7 +17679,7 @@ snapshots: jest-serializer-html: 7.1.0 jest-watch-typeahead: 2.2.2(jest@29.7.0) nyc: 15.1.0 - playwright: 1.45.1 + playwright: 1.48.2 transitivePeerDependencies: - '@swc/helpers' - '@types/node' @@ -18724,17 +18724,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@1.2.0(webpack-cli@4.9.1(webpack@5.94.0))(webpack@5.94.0(webpack-cli@4.9.1))': + '@webpack-cli/configtest@1.2.0(webpack-cli@4.9.1)(webpack@5.94.0)': dependencies: webpack: 5.94.0(webpack-cli@4.9.1) webpack-cli: 4.9.1(webpack@5.94.0) - '@webpack-cli/info@1.5.0(webpack-cli@4.9.1(webpack@5.94.0))': + '@webpack-cli/info@1.5.0(webpack-cli@4.9.1)': dependencies: envinfo: 7.14.0 webpack-cli: 4.9.1(webpack@5.94.0) - '@webpack-cli/serve@1.7.0(webpack-cli@4.9.1(webpack@5.94.0))': + '@webpack-cli/serve@1.7.0(webpack-cli@4.9.1)': dependencies: webpack-cli: 4.9.1(webpack@5.94.0) @@ -19694,7 +19694,7 @@ snapshots: moment: 2.29.4 moment-timezone: 0.5.46 - '@wordpress/dependency-extraction-webpack-plugin@6.14.0(webpack@5.94.0(webpack-cli@4.9.1))': + '@wordpress/dependency-extraction-webpack-plugin@6.14.0(webpack@5.94.0)': dependencies: json2php: 0.0.7 webpack: 5.94.0(webpack-cli@4.9.1) @@ -19993,7 +19993,7 @@ snapshots: dependencies: '@babel/runtime': 7.25.7 - '@wordpress/eslint-plugin@22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0))(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3)': + '@wordpress/eslint-plugin@22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0)(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3)': dependencies: '@babel/core': 7.26.0 '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@9.16.0) @@ -21075,14 +21075,14 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.1.2(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)): + babel-loader@9.1.2(@babel/core@7.26.0)(webpack@5.94.0): dependencies: '@babel/core': 7.26.0 find-cache-dir: 3.3.2 schema-utils: 4.2.0 webpack: 5.94.0(webpack-cli@4.9.1) - babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)): + babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.94.0): dependencies: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 @@ -21717,7 +21717,7 @@ snapshots: cookie@1.0.1: {} - copy-webpack-plugin@11.0.0(webpack@5.94.0(webpack-cli@4.9.1)): + copy-webpack-plugin@11.0.0(webpack@5.94.0): dependencies: fast-glob: 3.3.2 glob-parent: 6.0.2 @@ -21796,7 +21796,7 @@ snapshots: dependencies: postcss: 8.4.47 - css-loader@6.11.0(webpack@5.94.0(webpack-cli@4.9.1)): + css-loader@6.11.0(webpack@5.94.0): dependencies: icss-utils: 5.1.0(postcss@8.4.47) postcss: 8.4.47 @@ -21809,7 +21809,7 @@ snapshots: optionalDependencies: webpack: 5.94.0(webpack-cli@4.9.1) - css-loader@6.5.1(webpack@5.94.0(webpack-cli@4.9.1)): + css-loader@6.5.1(webpack@5.94.0): dependencies: icss-utils: 5.1.0(postcss@8.4.47) postcss: 8.4.47 @@ -21821,7 +21821,7 @@ snapshots: semver: 7.5.2 webpack: 5.94.0(webpack-cli@4.9.1) - css-minimizer-webpack-plugin@5.0.1(webpack@5.94.0(webpack-cli@4.9.1)): + css-minimizer-webpack-plugin@5.0.1(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 cssnano: 6.1.2(postcss@8.4.47) @@ -22428,7 +22428,7 @@ snapshots: es6-error@4.1.1: {} - esbuild-loader@3.0.1(webpack@5.94.0(webpack-cli@4.9.1)): + esbuild-loader@3.0.1(webpack@5.94.0): dependencies: esbuild: 0.17.19 get-tsconfig: 4.8.1 @@ -22582,7 +22582,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0))(eslint@9.16.0): + eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -22610,7 +22610,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.16.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0))(eslint@9.16.0) + eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -23149,7 +23149,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.0.4)(webpack@5.94.0): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -23166,7 +23166,7 @@ snapshots: typescript: 5.0.4 webpack: 5.94.0(webpack-cli@4.9.1) - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)): + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.0.4)(webpack@5.94.0): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -23510,7 +23510,7 @@ snapshots: html-tags@3.3.1: {} - html-webpack-plugin@5.6.3(webpack@5.94.0(webpack-cli@4.9.1)): + html-webpack-plugin@5.6.3(webpack@5.94.0): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -23588,7 +23588,7 @@ snapshots: dependencies: ms: 2.1.3 - husky@8.0.3: {} + husky@9.1.7: {} iconv-lite@0.4.24: dependencies: @@ -24196,7 +24196,7 @@ snapshots: jest-process-manager: 0.4.0 jest-runner: 29.7.0 nyc: 15.1.0 - playwright-core: 1.49.0 + playwright-core: 1.49.1 rimraf: 3.0.2 uuid: 8.3.2 transitivePeerDependencies: @@ -25154,7 +25154,7 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.1(webpack@5.94.0(webpack-cli@4.9.1)): + mini-css-extract-plugin@2.9.1(webpack@5.94.0): dependencies: schema-utils: 4.2.0 tapable: 2.2.1 @@ -25657,13 +25657,13 @@ snapshots: dependencies: find-up: 6.3.0 - playwright-core@1.45.1: {} + playwright-core@1.48.2: {} - playwright-core@1.49.0: {} + playwright-core@1.49.1: {} - playwright@1.45.1: + playwright@1.48.2: dependencies: - playwright-core: 1.45.1 + playwright-core: 1.48.2 optionalDependencies: fsevents: 2.3.2 @@ -25723,7 +25723,7 @@ snapshots: optionalDependencies: postcss: 8.4.47 - postcss-loader@6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)): + postcss-loader@6.2.0(postcss@8.4.47)(webpack@5.94.0): dependencies: cosmiconfig: 7.1.0 klona: 2.0.6 @@ -26707,7 +26707,7 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)): + sass-loader@12.4.0(sass@1.64.1)(webpack@5.94.0): dependencies: klona: 2.0.6 neo-async: 2.6.2 @@ -27174,13 +27174,13 @@ snapshots: style-inject@0.3.0: {} - style-loader@2.0.0(webpack@5.94.0(webpack-cli@4.9.1)): + style-loader@2.0.0(webpack@5.94.0): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 webpack: 5.94.0(webpack-cli@4.9.1) - style-loader@3.3.4(webpack@5.94.0(webpack-cli@4.9.1)): + style-loader@3.3.4(webpack@5.94.0): dependencies: webpack: 5.94.0(webpack-cli@4.9.1) @@ -27348,7 +27348,7 @@ snapshots: dependencies: memoizerific: 1.11.3 - terser-webpack-plugin@5.3.10(webpack@5.94.0(webpack-cli@4.9.1)): + terser-webpack-plugin@5.3.10(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -27357,7 +27357,7 @@ snapshots: terser: 5.37.0 webpack: 5.94.0(webpack-cli@4.9.1) - terser-webpack-plugin@5.3.3(webpack@5.94.0(webpack-cli@4.9.1)): + terser-webpack-plugin@5.3.3(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -27385,7 +27385,7 @@ snapshots: text-hex@1.0.0: {} - thread-loader@3.0.4(webpack@5.94.0(webpack-cli@4.9.1)): + thread-loader@3.0.4(webpack@5.94.0): dependencies: json-parse-better-errors: 1.0.2 loader-runner: 4.3.0 @@ -27871,9 +27871,9 @@ snapshots: webpack-cli@4.9.1(webpack@5.94.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.9.1(webpack@5.94.0))(webpack@5.94.0(webpack-cli@4.9.1)) - '@webpack-cli/info': 1.5.0(webpack-cli@4.9.1(webpack@5.94.0)) - '@webpack-cli/serve': 1.7.0(webpack-cli@4.9.1(webpack@5.94.0)) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.9.1)(webpack@5.94.0) + '@webpack-cli/info': 1.5.0(webpack-cli@4.9.1) + '@webpack-cli/serve': 1.7.0(webpack-cli@4.9.1) colorette: 2.0.20 commander: 7.2.0 execa: 5.1.1 @@ -27884,7 +27884,7 @@ snapshots: webpack: 5.94.0(webpack-cli@4.9.1) webpack-merge: 5.10.0 - webpack-dev-middleware@5.3.4(webpack@5.94.0(webpack-cli@4.9.1)): + webpack-dev-middleware@5.3.4(webpack@5.94.0): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -27893,7 +27893,7 @@ snapshots: schema-utils: 4.2.0 webpack: 5.94.0(webpack-cli@4.9.1) - webpack-dev-middleware@6.1.3(webpack@5.94.0(webpack-cli@4.9.1)): + webpack-dev-middleware@6.1.3(webpack@5.94.0): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -27946,7 +27946,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.94.0(webpack-cli@4.9.1)) + terser-webpack-plugin: 5.3.10(webpack@5.94.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: diff --git a/projects/github-actions/repo-gardening/README.md b/projects/github-actions/repo-gardening/README.md index 174af439a2056..df9209c409cd8 100644 --- a/projects/github-actions/repo-gardening/README.md +++ b/projects/github-actions/repo-gardening/README.md @@ -104,7 +104,7 @@ To get the channel ID of the channel where you'd like to post, copy one of the m Certain tasks require filesystem access to the PR, which `pull_request_target` does not provide. To accommodate this, you'll need to include a step to check the PR out in a subdirectory, like ```yaml - - name: Checkout the PR + - name: Check out the PR if: github.event_name == 'pull_request_target' uses: actions/checkout@v4 with: diff --git a/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels new file mode 100644 index 0000000000000..1723c0443417c --- /dev/null +++ b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: Gardening: Prevent error if more than 100 labels are added to a PR. + + diff --git a/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels2 b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels2 new file mode 100644 index 0000000000000..c0090e71358eb --- /dev/null +++ b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels2 @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: Gardening: Add an "all the things" label if more than 90 labels are on a PR. + + diff --git a/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js b/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js index 30a95cbbb4357..ea6164ab6b999 100644 --- a/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js +++ b/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js @@ -1,6 +1,7 @@ const { getInput } = require( '@actions/core' ); const debug = require( '../../utils/debug' ); const getFiles = require( '../../utils/get-files' ); +const getLabels = require( '../../utils/labels/get-labels' ); /* global GitHub, WebhookPayloadPullRequest */ @@ -49,7 +50,7 @@ function cleanName( name ) { } /** - * Build a list of labels to add to the issue, based off our file list. + * Build a list of labels to add to the pull request, based off our file list. * * @param {GitHub} octokit - Initialized Octokit REST client. * @param {string} owner - Repository owner. @@ -59,7 +60,7 @@ function cleanName( name ) { * @param {boolean} isRevert - Whether the pull request is a revert. * @return {Promise} Promise resolving to an array of keywords we'll search for. */ -async function getLabelsToAdd( octokit, owner, repo, number, isDraft, isRevert ) { +async function getFileDerivedLabels( octokit, owner, repo, number, isDraft, isRevert ) { const keywords = new Set(); // Get next valid milestone. @@ -320,7 +321,7 @@ async function getLabelsToAdd( octokit, owner, repo, number, isDraft, isRevert ) } /** - * Assigns any issues that are being worked to the author of the matching PR. + * Adds appropriate labels to the specified PR. * * @param {WebhookPayloadPullRequest} payload - Pull request event payload. * @param {GitHub} octokit - Initialized Octokit REST client. @@ -330,26 +331,83 @@ async function addLabels( payload, octokit ) { const { owner, name } = repository; const { draft, title } = pull_request; + // GitHub allows 100 labels on a PR. + // Limit to less than that to allow a buffer for future manual labels. + const maxLabels = 90; + const bigProjectLabel = '[Project] All the things!'; + // Get labels to add to the PR. const isDraft = !! ( pull_request && draft ); // If the PR title includes the word "revert", mark it as such. const isRevert = title.toLowerCase().includes( 'revert' ); - const labels = await getLabelsToAdd( octokit, owner.login, name, number, isDraft, isRevert ); + const fileDerivedLabels = await getFileDerivedLabels( + octokit, + owner.login, + name, + number, + isDraft, + isRevert + ); + + // Grab current labels on the PR. + // We can't rely on payload, as it could be outdated by the time this runs. + const currentLabels = await getLabels( octokit, owner.login, name, number ); + + // This is an array of labels that GitHub doesn't already have. + let labelsToAdd = fileDerivedLabels.filter( label => ! currentLabels.includes( label ) ); - if ( ! labels.length ) { - debug( 'add-labels: Could not find labels to add to that PR. Aborting' ); + // Nothing new was added, so abort. + if ( labelsToAdd.length === 0 ) { + debug( 'add-labels: No new labels to add to that PR. Aborting.' ); return; } - debug( `add-labels: Adding labels ${ labels } to PR #${ number }` ); + // Determine how many labels can safely be added. + let maxLabelsToAdd = Math.max( 0, maxLabels - currentLabels.length ); + + // Overkill, but let's prevent this label from counting toward the max. + const hasBigProjectLabel = currentLabels.includes( bigProjectLabel ); + if ( hasBigProjectLabel ) { + maxLabelsToAdd++; + } + + // If there are too many labels, we need to reduce the label count to keep GitHub happy. + if ( labelsToAdd.length > maxLabelsToAdd ) { + debug( `add-labels: Too many labels! Grouping project labels into '${ bigProjectLabel }'.` ); + + // Filter out project-type labels in deference to bigProjectLabel. + // In theory we could also remove any existing project-type labels here, but for now + // let's not as that would prevent manually adding specific project labels. + const projectLabelRegex = /^(\[Action\]|\[Package\]|\[Plugin\]|\[JS Package\])/; + labelsToAdd = labelsToAdd.filter( label => ! projectLabelRegex.test( label ) ); + + if ( ! hasBigProjectLabel ) { + // Add to the beginning of the labels array in case the array gets truncated later on. + labelsToAdd.unshift( bigProjectLabel ); + } + } else if ( hasBigProjectLabel ) { + await octokit.rest.issues.removeLabel( { + owner: owner.login, + repo: name, + issue_number: number, + name: bigProjectLabel, + } ); + } + // In the rare chance there would still be too many labels... + if ( labelsToAdd.length > maxLabelsToAdd ) { + debug( `add-labels: Limiting to the first ${ maxLabels }.` ); + labelsToAdd.splice( maxLabelsToAdd ); + } + + debug( `add-labels: Adding labels ${ labelsToAdd } to PR #${ number }` ); await octokit.rest.issues.addLabels( { owner: owner.login, repo: name, issue_number: number, - labels, + labels: labelsToAdd, } ); } diff --git a/projects/js-packages/charts/changelog/add-charts-build b/projects/js-packages/charts/changelog/add-charts-build new file mode 100644 index 0000000000000..fb97eecc14506 --- /dev/null +++ b/projects/js-packages/charts/changelog/add-charts-build @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Adding build option for Charts. diff --git a/projects/js-packages/charts/composer.json b/projects/js-packages/charts/composer.json index f2d38414dc78c..41bb0f0dadc60 100644 --- a/projects/js-packages/charts/composer.json +++ b/projects/js-packages/charts/composer.json @@ -13,6 +13,12 @@ ] }, "scripts": { + "build-development": [ + "pnpm run build" + ], + "build-production": [ + "NODE_ENV=production pnpm run build" + ], "test-coverage": [ "pnpm run test-coverage" ], diff --git a/projects/js-packages/charts/index.ts b/projects/js-packages/charts/index.ts new file mode 100644 index 0000000000000..c5666e2773e38 --- /dev/null +++ b/projects/js-packages/charts/index.ts @@ -0,0 +1,19 @@ +// Charts +export { BarChart } from './src/components/bar-chart'; +export { LineChart } from './src/components/line-chart'; +export { PieChart } from './src/components/pie-chart'; +export { PieSemiCircleChart } from './src/components/pie-semi-circle-chart'; + +// Chart components +export { BaseTooltip } from './src/components/tooltip'; +export { Legend } from './src/components/legend'; + +// Providers +export { ThemeProvider } from './src/providers/theme'; + +// Hooks +export { default as useChartMouseHandler } from './src/hooks/use-chart-mouse-handler'; + +// Types +export type * from './src/components/shared/types'; +export type { BaseTooltipProps } from './src/components/tooltip'; diff --git a/projects/js-packages/charts/package.json b/projects/js-packages/charts/package.json index c9acca5d411b5..f6a044d43f10d 100644 --- a/projects/js-packages/charts/package.json +++ b/projects/js-packages/charts/package.json @@ -17,7 +17,10 @@ "clean": "rm -rf node_modules", "test": "jest --config=tests/jest.config.cjs", "test-coverage": "pnpm run test --coverage", - "storybook": "cd ../storybook && pnpm run storybook:dev" + "storybook": "cd ../storybook && pnpm run storybook:dev", + "compile-ts": "tsc --pretty", + "build": "pnpm run clean-build && pnpm run compile-ts", + "clean-build": "rm -rf build/" }, "dependencies": { "@react-spring/web": "9.7.3", diff --git a/projects/js-packages/charts/src/components/bar-chart/index.tsx b/projects/js-packages/charts/src/components/bar-chart/index.tsx index df4a1f97ba3ad..35afda744bf9e 100644 --- a/projects/js-packages/charts/src/components/bar-chart/index.tsx +++ b/projects/js-packages/charts/src/components/bar-chart/index.tsx @@ -1 +1 @@ -export { default } from './bar-chart'; +export { default as BarChart } from './bar-chart'; diff --git a/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx index 0ca8d40d60462..33dd147e604b4 100644 --- a/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx @@ -1,4 +1,4 @@ -import BarChart from '../index'; +import { BarChart } from '../index'; import data from './sample-data'; import type { Meta, StoryObj } from '@storybook/react'; diff --git a/projects/js-packages/charts/src/index.ts b/projects/js-packages/charts/src/index.ts index b52a51461252c..cd8a712946e4b 100644 --- a/projects/js-packages/charts/src/index.ts +++ b/projects/js-packages/charts/src/index.ts @@ -1,16 +1,18 @@ // Charts -export { default as BarChart } from './components/bar-chart'; +export { BarChart } from './components/bar-chart'; export { LineChart } from './components/line-chart'; export { PieChart } from './components/pie-chart'; export { PieSemiCircleChart } from './components/pie-semi-circle-chart'; // Chart components export { BaseTooltip } from './components/tooltip'; +export { Legend } from './components/legend'; // Providers export { ThemeProvider } from './providers/theme'; // Hooks +export { default as useChartMouseHandler } from './hooks/use-chart-mouse-handler'; // Types export type * from './components/shared/types'; diff --git a/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx b/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx index 31a1aa1c320b1..cd14af571d52f 100644 --- a/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { ThemeProvider, jetpackTheme, wooTheme } from '../.'; -import { LineChart, BarChart, PieSemiCircleChart } from '../../..'; +import { LineChart, BarChart, PieSemiCircleChart } from '../../../.'; import barSampleData from '../../../components/bar-chart/stories/sample-data'; const meta: Meta< typeof LineChart > = { diff --git a/projects/js-packages/charts/tsconfig.json b/projects/js-packages/charts/tsconfig.json index 0e1116eec9836..6fa28c02613bd 100644 --- a/projects/js-packages/charts/tsconfig.json +++ b/projects/js-packages/charts/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "./build/" }, // List all sources and source-containing subdirs. - "include": [ "./src" ] + "include": [ "./index.ts", "./src" ] } diff --git a/projects/js-packages/critical-css-gen/changelog/renovate-playwright-monorepo b/projects/js-packages/critical-css-gen/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/critical-css-gen/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/critical-css-gen/package.json b/projects/js-packages/critical-css-gen/package.json index dfe7ccb4218b0..861323500e52f 100644 --- a/projects/js-packages/critical-css-gen/package.json +++ b/projects/js-packages/critical-css-gen/package.json @@ -34,7 +34,7 @@ "express": "4.21.2", "jest": "29.7.0", "path-browserify": "1.0.1", - "playwright": "1.45.1", + "playwright": "1.48.2", "playwright-core": "^1.45.1", "process": "0.11.10", "source-map": "0.7.4", diff --git a/projects/js-packages/storybook/changelog/renovate-playwright-monorepo b/projects/js-packages/storybook/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/storybook/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/storybook/package.json b/projects/js-packages/storybook/package.json index 7984577ae5ffd..82541f0584358 100644 --- a/projects/js-packages/storybook/package.json +++ b/projects/js-packages/storybook/package.json @@ -25,7 +25,7 @@ "@babel/plugin-syntax-jsx": "7.25.9", "@babel/preset-react": "7.25.9", "@babel/runtime": "7.26.0", - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "@storybook/addon-a11y": "8.3.5", "@storybook/addon-docs": "8.3.5", "@storybook/addon-essentials": "8.3.5", diff --git a/projects/packages/classic-theme-helper/.phan/baseline.php b/projects/packages/classic-theme-helper/.phan/baseline.php index d1f7843b03b03..92a89530ede7f 100644 --- a/projects/packages/classic-theme-helper/.phan/baseline.php +++ b/projects/packages/classic-theme-helper/.phan/baseline.php @@ -12,9 +12,9 @@ // PhanUndeclaredClassMethod : 20+ occurrences // PhanTypeMismatchArgumentInternal : 10+ occurrences // PhanTypePossiblyInvalidDimOffset : 8 occurrences + // PhanTypeSuspiciousNonTraversableForeach : 6 occurrences // PhanTypeMismatchArgumentProbablyReal : 5 occurrences // PhanUndeclaredClassReference : 4 occurrences - // PhanTypeSuspiciousNonTraversableForeach : 3 occurrences // PhanTypeInvalidDimOffset : 2 occurrences // PhanTypeMismatchArgument : 2 occurrences // PhanTypeComparisonToArray : 1 occurrence @@ -30,6 +30,7 @@ 'src/custom-content-types.php' => ['PhanUndeclaredClassMethod'], 'src/custom-post-types/class-jetpack-portfolio.php' => ['PhanTypeMismatchArgumentProbablyReal', 'PhanTypeSuspiciousNonTraversableForeach', 'PhanUndeclaredClassMethod'], 'src/custom-post-types/class-jetpack-testimonial.php' => ['PhanTypeMismatchArgumentProbablyReal', 'PhanUndeclaredClassMethod'], + 'src/custom-post-types/class-nova-restaurant.php' => ['PhanTypeSuspiciousNonTraversableForeach'], 'src/site-breadcrumbs.php' => ['PhanUndeclaredClassMethod'], ], // 'directory_suppressions' => ['src/directory_name' => ['PhanIssueName1', 'PhanIssueName2']] can be manually added if needed. diff --git a/projects/packages/classic-theme-helper/changelog/add-restaurant-menus-cpt-to-classic-theme-helper-package b/projects/packages/classic-theme-helper/changelog/add-restaurant-menus-cpt-to-classic-theme-helper-package new file mode 100644 index 0000000000000..9e79126189b5d --- /dev/null +++ b/projects/packages/classic-theme-helper/changelog/add-restaurant-menus-cpt-to-classic-theme-helper-package @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Custom Post Types: Added Restaurant Menu CPT files. diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/class-nova-restaurant.php b/projects/packages/classic-theme-helper/src/custom-post-types/class-nova-restaurant.php new file mode 100644 index 0000000000000..924209b5503dc --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/class-nova-restaurant.php @@ -0,0 +1,1760 @@ + 'section', + * 'menu_class' => 'menu-items', + * 'menu_header_tag' => 'header', + * 'menu_header_class' => 'menu-group-header', + * 'menu_title_tag' => 'h1', + * 'menu_title_class' => 'menu-group-title', + * 'menu_description_tag' => 'div', + * 'menu_description_class' => 'menu-group-description', + * ) ); + * } + * + * @todo + * - Bulk/Quick edit response of Menu Item rows is broken. + * - Drag and Drop reordering. + * + * @package automattic/jetpack-classic-theme-helper + */ + +namespace Automattic\Jetpack\Classic_Theme_Helper; + +use Automattic\Jetpack\Assets; +use Automattic\Jetpack\Roles; +use WP_Post; +use WP_Query; + +if ( ! class_exists( __NAMESPACE__ . '\Nova_Restaurant' ) ) { + + /** + * Create the new Nova CPT. + */ + class Nova_Restaurant { + const MENU_ITEM_POST_TYPE = 'nova_menu_item'; + const MENU_ITEM_LABEL_TAX = 'nova_menu_item_label'; + const MENU_TAX = 'nova_menu'; + + /** + * Version number used when enqueuing all resources (css and js). + * + * @var string + */ + public $version = '20210303'; + + /** + * Default markup for the menu items. + * + * @var array + */ + protected $default_menu_item_loop_markup = array( + 'menu_tag' => 'section', + 'menu_class' => 'menu-items', + 'menu_header_tag' => 'header', + 'menu_header_class' => 'menu-group-header', + 'menu_title_tag' => 'h1', + 'menu_title_class' => 'menu-group-title', + 'menu_description_tag' => 'div', + 'menu_description_class' => 'menu-group-description', + ); + + /** + * Array of markup for the menu items. + * + * @var array + */ + protected $menu_item_loop_markup = array(); + + /** + * Last term ID of a loop of menu items. + * + * @var bool|int + */ + protected $menu_item_loop_last_term_id = false; + + /** + * Current term ID of a loop of menu items. + * + * @var bool|int|\WP_Term + */ + protected $menu_item_loop_current_term = false; + + /** + * Initialize class. + * + * @param array $menu_item_loop_markup Array of markup for the menu items. + * + * @return self + */ + public static function init( $menu_item_loop_markup = array() ) { + static $instance = false; + + if ( ! $instance ) { + $instance = new Nova_Restaurant(); + } + + if ( $menu_item_loop_markup ) { + $instance->menu_item_loop_markup = wp_parse_args( $menu_item_loop_markup, $instance->default_menu_item_loop_markup ); + } + + return $instance; + } + + /** + * Constructor. + * Hook into WordPress to create CPT and utilities if needed. + */ + public function __construct() { + if ( ! $this->site_supports_nova() ) { + return; + } + + $this->register_taxonomies(); + $this->register_post_types(); + add_action( 'admin_menu', array( $this, 'add_admin_menus' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_nova_styles' ) ); + add_action( 'admin_head', array( $this, 'set_custom_font_icon' ) ); + + // Always sort menu items correctly + add_action( 'parse_query', array( $this, 'sort_menu_item_queries_by_menu_order' ) ); + add_filter( 'posts_results', array( $this, 'sort_menu_item_queries_by_menu_taxonomy' ), 10, 2 ); + + add_action( 'wp_insert_post', array( $this, 'add_post_meta' ) ); + + $this->menu_item_loop_markup = $this->default_menu_item_loop_markup; + + // Only output our Menu Item Loop Markup on a real blog view. Not feeds, XML-RPC, admin, etc. + add_filter( 'template_include', array( $this, 'setup_menu_item_loop_markup__in_filter' ) ); + + add_filter( 'enter_title_here', array( $this, 'change_default_title' ) ); + add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) ); + add_filter( 'dashboard_glance_items', array( $this, 'add_to_dashboard' ) ); + } + + /** + * Should this Custom Post Type be made available? + * + * @return bool + */ + public function site_supports_nova() { + // If we're on WordPress.com, and it has the menu site vertical. + if ( function_exists( 'site_vertical' ) && 'nova_menu' === site_vertical() ) { // @phan-suppress-current-line PhanUndeclaredFunction -- only calling if it exists. + return true; + } + + // Else, if the current theme requests it. + if ( current_theme_supports( self::MENU_ITEM_POST_TYPE ) ) { + return true; + } + + // Otherwise, say no unless something wants to filter us to say yes. + /** + * Allow something else to hook in and enable this CPT. + * + * @module custom-content-types + * + * @since 2.6.0 + * + * @param bool false Whether or not to enable this CPT. + * @param string $var The slug for this CPT. + */ + return (bool) apply_filters( 'jetpack_enable_cpt', false, self::MENU_ITEM_POST_TYPE ); + } + + /* Setup */ + + /** + * Register Taxonomies and Post Type + */ + public function register_taxonomies() { + if ( ! taxonomy_exists( self::MENU_ITEM_LABEL_TAX ) ) { + register_taxonomy( + self::MENU_ITEM_LABEL_TAX, + self::MENU_ITEM_POST_TYPE, + array( + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Item Labels', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Item Labels', 'jetpack-classic-theme-helper' ), + 'popular_items' => __( 'Popular Labels', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'All Menu Item Labels', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'update_item' => __( 'Update Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add New Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'new_item_name' => __( 'New Menu Item Label Name', 'jetpack-classic-theme-helper' ), + 'separate_items_with_commas' => __( 'For example, spicy, favorite, etc.
Separate Labels with commas', 'jetpack-classic-theme-helper' ), + 'add_or_remove_items' => __( 'Add or remove Labels', 'jetpack-classic-theme-helper' ), + 'choose_from_most_used' => __( 'Choose from the most used Labels', 'jetpack-classic-theme-helper' ), + 'items_list_navigation' => __( 'Menu item label list navigation', 'jetpack-classic-theme-helper' ), + 'items_list' => __( 'Menu item labels list', 'jetpack-classic-theme-helper' ), + ), + 'no_tagcloud' => __( 'No Labels found', 'jetpack-classic-theme-helper' ), + 'hierarchical' => false, + ) + ); + } + + if ( ! taxonomy_exists( self::MENU_TAX ) ) { + register_taxonomy( + self::MENU_TAX, + self::MENU_ITEM_POST_TYPE, + array( + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Sections', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Sections', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'All Menu Sections', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'parent_item' => __( 'Parent Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'parent_item_colon' => __( 'Parent Menu Section:', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'update_item' => __( 'Update Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add New Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'new_item_name' => __( 'New Menu Sections Name', 'jetpack-classic-theme-helper' ), + 'items_list_navigation' => __( 'Menu section list navigation', 'jetpack-classic-theme-helper' ), + 'items_list' => __( 'Menu section list', 'jetpack-classic-theme-helper' ), + ), + 'rewrite' => array( + 'slug' => 'menu', + 'with_front' => false, + 'hierarchical' => true, + ), + 'hierarchical' => true, + 'show_tagcloud' => false, + 'query_var' => 'menu', + ) + ); + } + } + + /** + * Register our Post Type. + */ + public function register_post_types() { + if ( post_type_exists( self::MENU_ITEM_POST_TYPE ) ) { + return; + } + + register_post_type( + self::MENU_ITEM_POST_TYPE, + array( + 'description' => __( "Items on your restaurant's menu", 'jetpack-classic-theme-helper' ), + + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Items', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'menu_name' => __( 'Food Menus', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'Menu Items', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new' => __( 'Add One Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'new_item' => __( 'New Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Items', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'not_found' => __( 'No Menu Items found', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'not_found_in_trash' => __( 'No Menu Items found in Trash', 'jetpack-classic-theme-helper' ), + 'filter_items_list' => __( 'Filter menu items list', 'jetpack-classic-theme-helper' ), + 'items_list_navigation' => __( 'Menu item list navigation', 'jetpack-classic-theme-helper' ), + 'items_list' => __( 'Menu items list', 'jetpack-classic-theme-helper' ), + ), + 'supports' => array( + 'title', + 'editor', + 'thumbnail', + 'excerpt', + ), + 'rewrite' => array( + 'slug' => 'item', + 'with_front' => false, + 'feeds' => false, + 'pages' => false, + ), + 'register_meta_box_cb' => array( $this, 'register_menu_item_meta_boxes' ), + + 'public' => true, + 'show_ui' => true, // set to false to replace with custom UI + 'menu_position' => 20, // below Pages + 'capability_type' => 'page', + 'map_meta_cap' => true, + 'has_archive' => false, + 'query_var' => 'item', + ) + ); + } + + /** + * Update messages for the Menu Item admin. + * + * @param array $messages Existing post update messages. + * + * @return array $messages Updated post update messages. + */ + public function updated_messages( $messages ) { + global $post; + + $messages[ self::MENU_ITEM_POST_TYPE ] = array( + 0 => '', // Unused. Messages start at index 1. + 1 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item updated. View item', 'jetpack-classic-theme-helper' ), + esc_url( get_permalink( $post->ID ) ) + ), + 2 => esc_html__( 'Custom field updated.', 'jetpack-classic-theme-helper' ), + 3 => esc_html__( 'Custom field deleted.', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 4 => esc_html__( 'Menu item updated.', 'jetpack-classic-theme-helper' ), + 5 => isset( $_GET['revision'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. + ? sprintf( + /* translators: %s: date and time of the revision */ + esc_html__( 'Menu item restored to revision from %s', 'jetpack-classic-theme-helper' ), + wp_post_revision_title( (int) $_GET['revision'], false ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. + ) + : false, + 6 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item published. View item', 'jetpack-classic-theme-helper' ), + esc_url( get_permalink( $post->ID ) ) + ), + /* translators: this is about a food menu */ + 7 => esc_html__( 'Menu item saved.', 'jetpack-classic-theme-helper' ), + 8 => sprintf( + /* translators: this is about a food menu */ + __( 'Menu item submitted. Preview item', 'jetpack-classic-theme-helper' ), + esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) + ), + 9 => sprintf( + /* translators: this is about a food menu. 1. Publish box date format, see https://php.net/date 2. link to the food menu. */ + __( 'Menu item scheduled for: %1$s. Preview item', 'jetpack-classic-theme-helper' ), + /* translators: Publish box date format, see https://php.net/date */ + date_i18n( __( 'M j, Y @ G:i', 'jetpack-classic-theme-helper' ), strtotime( $post->post_date ) ), + esc_url( get_permalink( $post->ID ) ) + ), + 10 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item draft updated. Preview item', 'jetpack-classic-theme-helper' ), + esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) + ), + ); + + return $messages; + } + + /** + * Nova styles and scripts. + * + * @param string $hook Page hook. + * + * @return void + */ + public function enqueue_nova_styles( $hook ) { + global $post_type; + $pages = array( 'edit.php', 'post.php', 'post-new.php' ); + + if ( in_array( $hook, $pages, true ) && $post_type === self::MENU_ITEM_POST_TYPE ) { + wp_enqueue_style( 'nova-style', plugins_url( 'css/nova.css', __FILE__ ), array(), $this->version ); + } + + wp_enqueue_style( 'nova-font', plugins_url( 'css/nova-font.css', __FILE__ ), array(), $this->version ); + } + + /** + * Change ‘Enter Title Here’ text for the Menu Item. + * + * @param string $title Default title placeholder text. + * + * @return string + */ + public function change_default_title( $title ) { + if ( self::MENU_ITEM_POST_TYPE === get_post_type() ) { + /* translators: this is about a food menu */ + $title = esc_html__( "Enter the menu item's name here", 'jetpack-classic-theme-helper' ); + } + + return $title; + } + + /** + * Add to Dashboard At A Glance + * + * @return void + */ + public function add_to_dashboard() { + $number_menu_items = wp_count_posts( self::MENU_ITEM_POST_TYPE ); + + $roles = new Roles(); // @phan-suppress-current-line PhanUndeclaredClassMethod -- declared at top of file. + if ( current_user_can( $roles->translate_role_to_cap( 'administrator' ) ) ) { // @phan-suppress-current-line PhanUndeclaredClassMethod + $number_menu_items_published = sprintf( + '%2$s', + esc_url( + get_admin_url( + get_current_blog_id(), + 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE + ) + ), + sprintf( + /* translators: Placehoder is a number of items. */ + _n( + '%1$d Food Menu Item', + '%1$d Food Menu Items', + (int) $number_menu_items->publish, + 'jetpack-classic-theme-helper' + ), + number_format_i18n( $number_menu_items->publish ) + ) + ); + } else { + $number_menu_items_published = sprintf( + '%1$s', + sprintf( + /* translators: Placehoder is a number of items. */ + _n( + '%1$d Food Menu Item', + '%1$d Food Menu Items', + (int) $number_menu_items->publish, + 'jetpack-classic-theme-helper' + ), + number_format_i18n( $number_menu_items->publish ) + ) + ); + } + + echo '
  • ' . $number_menu_items_published . '
  • '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we escape things above. + } + + /** + * If the WP query for our menu items. + * + * @param WP_Query $query WP Query. + * + * @return bool + */ + public function is_menu_item_query( $query ) { + if ( + ( isset( $query->query_vars['taxonomy'] ) && self::MENU_TAX === $query->query_vars['taxonomy'] ) + || + ( isset( $query->query_vars['post_type'] ) && self::MENU_ITEM_POST_TYPE === $query->query_vars['post_type'] ) + ) { + return true; + } + + return false; + } + + /** + * Custom sort the menu item queries by menu order. + * + * @param WP_Query $query WP Query. + * + * @return void + */ + public function sort_menu_item_queries_by_menu_order( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } + + $query->query_vars['orderby'] = 'menu_order'; + $query->query_vars['order'] = 'ASC'; + + // For now, just turn off paging so we can sort by taxonmy later + // If we want paging in the future, we'll need to add the taxonomy sort here (or at least before the DB query is made) + $query->query_vars['posts_per_page'] = -1; + } + + /** + * Custom sort the menu item queries by menu taxonomies. + * + * @param WP_Post[] $posts Array of post objects. + * @param WP_Query $query The WP_Query instance. + * + * @return WP_Post[] + */ + public function sort_menu_item_queries_by_menu_taxonomy( $posts, $query ) { + if ( ! $posts ) { + return $posts; + } + + if ( ! $this->is_menu_item_query( $query ) ) { + return $posts; + } + + $grouped_by_term = array(); + + foreach ( $posts as $post ) { + $term = $this->get_menu_item_menu_leaf( $post->ID ); + if ( ! $term || is_wp_error( $term ) ) { + $term_id = 0; + } else { + $term_id = $term->term_id; + } + + if ( ! isset( $grouped_by_term[ "$term_id" ] ) ) { + $grouped_by_term[ "$term_id" ] = array(); + } + + $grouped_by_term[ "$term_id" ][] = $post; + } + + $term_order = get_option( 'nova_menu_order', array() ); + + $return = array(); + foreach ( $term_order as $term_id ) { + if ( isset( $grouped_by_term[ "$term_id" ] ) ) { + $return = array_merge( $return, $grouped_by_term[ "$term_id" ] ); + unset( $grouped_by_term[ "$term_id" ] ); + } + } + + foreach ( $grouped_by_term as $term_id => $posts ) { + $return = array_merge( $return, $posts ); + } + + return $return; + } + + /** + * Add new "Add many items" submenu, custom colunmns, and custom bulk actions. + * + * @return void + */ + public function add_admin_menus() { + $hook = add_submenu_page( + 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE, + __( 'Add Many Items', 'jetpack-classic-theme-helper' ), + __( 'Add Many Items', 'jetpack-classic-theme-helper' ), + 'edit_pages', + 'add_many_nova_items', + array( $this, 'add_many_new_items_page' ) + ); + + add_action( "load-$hook", array( $this, 'add_many_new_items_page_load' ) ); + + add_action( 'current_screen', array( $this, 'current_screen_load' ) ); + + /* + * Adjust 'Add Many Items' submenu position + * We're making changes to the menu global, but no other choice unfortunately. + * phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited + */ + if ( isset( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ) ) { + $submenu_item = array_pop( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); + $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ][11] = $submenu_item; + ksort( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); + } + // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited + + $this->setup_menu_item_columns(); + + Assets::register_script( + 'nova-menu-checkboxes', + '../../dist/custom-post-types/js/menu-checkboxes.js', + __FILE__, + array( + 'in_footer' => true, + 'enqueue' => false, + 'textdomain' => 'jetpack-classic-theme-helper', + ) + ); + } + + /** + * Custom Nova Icon CSS + * + * @return void + */ + public function set_custom_font_icon() { + ?> + + id ) { + return; + } + + $this->edit_menu_items_page_load(); + add_filter( 'admin_notices', array( $this, 'admin_notices' ) ); + } + + /* Edit Items List */ + + /** + * Display a notice in wp-admin after items have been changed. + * + * @return void + */ + public function admin_notices() { + if ( isset( $_GET['nova_reordered'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values. + printf( + '

    %s

    ', + /* translators: this is about a food menu */ + esc_html__( 'Menu Items re-ordered.', 'jetpack-classic-theme-helper' ) + ); + } + } + + /** + * Do not allow sorting by title. + * + * @param array $columns An array of sortable columns. + * + * @return array $columns. + */ + public function no_title_sorting( $columns ) { + if ( isset( $columns['title'] ) ) { + unset( $columns['title'] ); + } + return $columns; + } + + /** + * Set up custom columns for our Nova menu. + * + * @return void + */ + public function setup_menu_item_columns() { + add_filter( sprintf( 'manage_edit-%s_sortable_columns', self::MENU_ITEM_POST_TYPE ), array( $this, 'no_title_sorting' ) ); + add_filter( sprintf( 'manage_%s_posts_columns', self::MENU_ITEM_POST_TYPE ), array( $this, 'menu_item_columns' ) ); + + add_action( sprintf( 'manage_%s_posts_custom_column', self::MENU_ITEM_POST_TYPE ), array( $this, 'menu_item_column_callback' ), 10, 2 ); + } + + /** + * Add custom columns to the Nova menu item list. + * + * @param array $columns An array of columns. + * + * @return array $columns. + */ + public function menu_item_columns( $columns ) { + unset( $columns['date'], $columns['likes'] ); + + $columns['thumbnail'] = __( 'Thumbnail', 'jetpack-classic-theme-helper' ); + $columns['labels'] = __( 'Labels', 'jetpack-classic-theme-helper' ); + $columns['price'] = __( 'Price', 'jetpack-classic-theme-helper' ); + $columns['order'] = __( 'Order', 'jetpack-classic-theme-helper' ); + + return $columns; + } + + /** + * Display custom data in each new custom column we created. + * + * @param string $column The name of the column to display. + * @param int $post_id The current post ID. + * + * @return void + */ + public function menu_item_column_callback( $column, $post_id ) { + $screen = get_current_screen(); + + switch ( $column ) { + case 'thumbnail': + echo get_the_post_thumbnail( $post_id, array( 50, 50 ) ); + break; + case 'labels': + $this->list_admin_labels( $post_id ); + break; + case 'price': + $this->display_price( $post_id ); + break; + case 'order': + $url = admin_url( $screen->parent_file ); + + $up_url = add_query_arg( + array( + 'action' => 'move-item-up', + 'post_id' => (int) $post_id, + ), + wp_nonce_url( $url, 'nova_move_item_up_' . $post_id ) + ); + + $down_url = add_query_arg( + array( + 'action' => 'move-item-down', + 'post_id' => (int) $post_id, + ), + wp_nonce_url( $url, 'nova_move_item_down_' . $post_id ) + ); + $menu_item = get_post( $post_id ); + $menu = $this->get_menu_by_post_id( $post_id ); + $term_id = is_object( $menu ) ? $menu->term_id : ''; + ?> + + + + +     — up +
    +     — down +
    + $menu_order ) { + $id = absint( $id ); + unset( $order_pairs[ $id ] ); + if ( $id < 0 ) { + continue; + } + + $post = get_post( $id ); + if ( ! $post ) { + continue; + } + + // save a write if the order hasn't changed + if ( (int) $menu_order !== $post->menu_order ) { + $args = array( + 'ID' => $id, + 'menu_order' => $menu_order, + ); + wp_update_post( $args ); + } + + // save a write if the term hasn't changed + if ( + is_object( $this->get_menu_by_post_id( $id ) ) && + (int) $term_pairs[ $id ] !== $this->get_menu_by_post_id( $id )->term_id + ) { + wp_set_object_terms( $id, $term_pairs[ $id ], self::MENU_TAX ); + } + } + + $redirect = add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'nova_reordered' => '1', + ), + admin_url( 'edit.php' ) + ); + wp_safe_redirect( $redirect ); + exit; + } + + /** + * Handle changes to menu items. + * (process actions, update data, enqueue necessary scripts). + * + * @return void + */ + public function edit_menu_items_page_load() { + if ( isset( $_GET['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we process the form and check nonces in handle_menu_item_actions. + $this->handle_menu_item_actions(); + } + + $this->maybe_reorder_menu_items(); + + Assets::register_script( + 'nova-drag-drop', + '../../dist/custom-post-types/js/nova-drag-drop.js', + __FILE__, + array( + 'dependencies' => array( + 'jquery', + 'jquery-ui-sortable', + ), + 'in_footer' => true, + 'enqueue' => true, + 'textdomain' => 'jetpack-classic-theme-helper', + ) + ); + + wp_localize_script( + 'nova-drag-drop', + '_novaDragDrop', + array( + 'nonce' => wp_create_nonce( 'drag-drop-reorder' ), + 'nonceName' => 'drag-drop-reorder', + 'reorder' => __( 'Save New Order', 'jetpack-classic-theme-helper' ), + 'reorderName' => 'menu_reorder_submit', + ) + ); + add_action( 'the_post', array( $this, 'show_menu_titles_in_menu_item_list' ) ); + } + + /** + * Process actions to move menu items around. + * + * @return void + */ + public function handle_menu_item_actions() { + if ( isset( $_GET['action'] ) ) { + $action = (string) wp_unslash( $_GET['action'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we check for nonces below, and check against specific strings in switch statement. + } else { + return; + } + + switch ( $action ) { + case 'move-item-up': + case 'move-item-down': + $reorder = false; + + if ( empty( $_GET['post_id'] ) ) { + break; + } + + $post_id = (int) $_GET['post_id']; + + $term = $this->get_menu_item_menu_leaf( $post_id ); + + // Get all posts in that term. + $query = new WP_Query( + array( + 'taxonomy' => self::MENU_TAX, + 'term' => $term->slug, + ) + ); + + $order = array(); + foreach ( $query->posts as $post ) { + $order[] = $post->ID; + } + + if ( 'move-item-up' === $action ) { + check_admin_referer( 'nova_move_item_up_' . $post_id ); + + $first_post_id = $order[0]; + if ( $post_id === $first_post_id ) { + break; + } + + foreach ( $order as $menu_order => $order_post_id ) { + if ( $post_id !== $order_post_id ) { + continue; + } + + $swap_post_id = $order[ $menu_order - 1 ]; + $order[ $menu_order - 1 ] = $post_id; + $order[ $menu_order ] = $swap_post_id; + + $reorder = true; + break; + } + } else { + check_admin_referer( 'nova_move_item_down_' . $post_id ); + + $last_post_id = end( $order ); + if ( $post_id === $last_post_id ) { + break; + } + + foreach ( $order as $menu_order => $order_post_id ) { + if ( $post_id !== $order_post_id ) { + continue; + } + + $swap_post_id = $order[ $menu_order + 1 ]; + $order[ $menu_order + 1 ] = $post_id; + $order[ $menu_order ] = $swap_post_id; + + $reorder = true; + } + } + + if ( $reorder ) { + foreach ( $order as $menu_order => $id ) { + wp_update_post( compact( 'id', 'menu_order' ) ); + } + } + + break; + case 'move-menu-up': + case 'move-menu-down': + $reorder = false; + + if ( empty( $_GET['term_id'] ) ) { + break; + } + + $term_id = (int) $_GET['term_id']; + + $terms = $this->get_menus(); + + $order = array(); + foreach ( $terms as $term ) { + $order[] = $term->term_id; + } + + if ( 'move-menu-up' === $action ) { + check_admin_referer( 'nova_move_menu_up_' . $term_id ); + + $first_term_id = $order[0]; + if ( $term_id === $first_term_id ) { + break; + } + + foreach ( $order as $menu_order => $order_term_id ) { + if ( $term_id !== $order_term_id ) { + continue; + } + + $swap_term_id = $order[ $menu_order - 1 ]; + $order[ $menu_order - 1 ] = $term_id; + $order[ $menu_order ] = $swap_term_id; + + $reorder = true; + break; + } + } else { + check_admin_referer( 'nova_move_menu_down_' . $term_id ); + + $last_term_id = end( $order ); + if ( $term_id === $last_term_id ) { + break; + } + + foreach ( $order as $menu_order => $order_term_id ) { + if ( $term_id !== $order_term_id ) { + continue; + } + + $swap_term_id = $order[ $menu_order + 1 ]; + $order[ $menu_order + 1 ] = $term_id; + $order[ $menu_order ] = $swap_term_id; + + $reorder = true; + } + } + + if ( $reorder ) { + update_option( 'nova_menu_order', $order ); + } + + break; + default: + return; + } + + $redirect = add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'nova_reordered' => '1', + ), + admin_url( 'edit.php' ) + ); + wp_safe_redirect( $redirect ); + exit; + } + + /** + * Add menu title rows to the list table + * + * @param \WP_Post $post The Post object. + * + * @return void + */ + public function show_menu_titles_in_menu_item_list( $post ) { + global $wp_list_table; + + static $last_term_id = false; + + $term = $this->get_menu_item_menu_leaf( $post->ID ); + + $term_id = $term instanceof \WP_Term ? $term->term_id : null; + + if ( false !== $last_term_id && $last_term_id === $term_id ) { + return; + } + + if ( $term_id === null ) { + $last_term_id = null; + $term_name = ''; + $parent_count = 0; + } else { + $last_term_id = $term->term_id; + $term_name = $term->name; + $parent_count = 0; + $current_term = $term; + while ( $current_term->parent ) { + ++$parent_count; + $current_term = get_term( $current_term->parent, self::MENU_TAX ); + } + } + + $non_order_column_count = $wp_list_table->get_column_count() - 1; + + $screen = get_current_screen(); + + $url = admin_url( $screen->parent_file ); + + $up_url = add_query_arg( + array( + 'action' => 'move-menu-up', + 'term_id' => (int) $term_id, + ), + wp_nonce_url( $url, 'nova_move_menu_up_' . $term_id ) + ); + + $down_url = add_query_arg( + array( + 'action' => 'move-menu-down', + 'term_id' => (int) $term_id, + ), + wp_nonce_url( $url, 'nova_move_menu_down_' . $term_id ) + ); + + ?> + + +

    + ', '', $term ); + + } else { + esc_html_e( 'Uncategorized', 'jetpack-classic-theme-helper' ); + } + ?> +

    + + + + +
    + + + + + process_form_request(); + exit; + } + + $this->enqueue_many_items_scripts(); + } + + /** + * Enqueue script to create many items at once. + * + * @return void + */ + public function enqueue_many_items_scripts() { + + Assets::register_script( + 'nova-many-items', + '../../dist/custom-post-types/js/many-items.js', + __FILE__, + array( + 'in_footer' => true, + 'enqueue' => true, + 'textdomain' => 'jetpack-classic-theme-helper', + ) + ); + } + + /** + * Process form request to create many items at once. + * + * @return void + */ + public function process_form_request() { + if ( ! isset( $_POST['nova_title'] ) || ! is_array( $_POST['nova_title'] ) ) { + return; + } + + $is_ajax = ! empty( $_POST['ajax'] ); + + if ( $is_ajax ) { + check_ajax_referer( 'nova_many_items' ); + } else { + check_admin_referer( 'nova_many_items' ); + } + + /* + * $_POST is already slashed + * phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash + */ + foreach ( array_keys( $_POST['nova_title'] ) as $key ) : // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we sanitize below. + $post_details = array( + 'post_status' => 'publish', + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'post_content' => ! empty( $_POST['nova_content'] ) && ! empty( $_POST['nova_content'][ $key ] ) + ? sanitize_text_field( $_POST['nova_content'][ $key ] ) + : '', + 'post_title' => isset( $_POST['nova_title'][ $key ] ) + ? sanitize_title( $_POST['nova_title'][ $key ] ) + : '', + 'tax_input' => array( + self::MENU_ITEM_LABEL_TAX => isset( $_POST['nova_labels'][ $key ] ) + ? sanitize_meta( self::MENU_ITEM_LABEL_TAX, $_POST['nova_labels'][ $key ], 'term' ) + : null, + self::MENU_TAX => isset( $_POST['nova_menu_tax'] ) + ? sanitize_meta( self::MENU_TAX, $_POST['nova_menu_tax'], 'term' ) + : null, + ), + ); + + $post_id = wp_insert_post( $post_details ); + if ( ! $post_id || is_wp_error( $post_id ) ) { + continue; + } + + $this->set_price( + $post_id, + isset( $_POST['nova_price'][ $key ] ) + ? sanitize_meta( 'nova_price', $_POST['nova_price'][ $key ], 'post' ) + : '' + ); + // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash + + if ( $is_ajax ) : + $post = get_post( $post_id ); + $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + setup_postdata( $post ); + + ?> + + display_price(); ?> + list_labels( $post_id ); ?> + + +
    +

    + +

    + TAB key on your keyboard to move between colums and the ENTER or RETURN key to save each row and move on to the next.', 'jetpack-classic-theme-helper' ), + array( + 'kbd' => array(), + ) + ); + ?> +

    + +
    +

    +

    + 'nova-menu-tax', + 'name' => 'nova_menu_tax', + 'taxonomy' => self::MENU_TAX, + 'hide_empty' => false, + 'hierarchical' => true, + ) + ); + ?> +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + spicy, favorite, etc. Separate Labels with commas', 'jetpack-classic-theme-helper' ), + array( + 'small' => array(), + 'em' => array(), + ) + ); + ?> +
    +
    +
    + +

    + + +

    +
    +
    + %2$s', + (int) $post->ID, + esc_html__( 'Price', 'jetpack-classic-theme-helper' ), + esc_attr( $this->get_price( (int) $post->ID ) ) + ); + } + + /** + * Save the price of a menu item. + * + * @param int $post_id Post ID. + */ + public function add_post_meta( $post_id ) { + if ( ! isset( $_POST['nova_price'][ $post_id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. + return; + } + + $this->set_price( + $post_id, + sanitize_meta( 'nova_price', wp_unslash( $_POST['nova_price'][ $post_id ] ), 'post' ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. + ); + } + + /* Data */ + + /** + * Get ordered array of menu items. + * + * @param array $args Optional argumments. + * + * @return array + */ + public function get_menus( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'hide_empty' => false, + ) + ); + $args['taxonomy'] = self::MENU_TAX; + + $terms = get_terms( $args ); // @phan-suppress-current-line PhanAccessMethodInternal + if ( ! $terms || is_wp_error( $terms ) ) { + return array(); + } + + $terms_by_id = array(); + foreach ( $terms as $term ) { + $terms_by_id[ "{$term->term_id}" ] = $term; + } + + $term_order = get_option( 'nova_menu_order', array() ); + + $return = array(); + foreach ( $term_order as $term_id ) { + if ( isset( $terms_by_id[ "$term_id" ] ) ) { + $return[] = $terms_by_id[ "$term_id" ]; + unset( $terms_by_id[ "$term_id" ] ); + } + } + + foreach ( $terms_by_id as $term_id => $term ) { + $return[] = $term; + } + + return $return; + } + + /** + * Get first menu taxonomy "leaf". + * + * @param int $post_id Post ID. + * + * @return bool|\WP_Term|\WP_Error|null + */ + public function get_menu_item_menu_leaf( $post_id ) { + // Get first menu taxonomy "leaf". + $term_ids = wp_get_object_terms( $post_id, self::MENU_TAX, array( 'fields' => 'ids' ) ); + + foreach ( $term_ids as $term_id ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach + $children = get_term_children( $term_id, self::MENU_TAX ); + if ( ! $children ) { + break; + } + } + + if ( ! isset( $term_id ) ) { + return false; + } + + return get_term( $term_id, self::MENU_TAX ); + } + + /** + * Get a list of the labels linked to a menu item. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function list_labels( $post_id = 0 ) { + $post = get_post( $post_id ); + echo get_the_term_list( $post->ID, self::MENU_ITEM_LABEL_TAX, '', _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), '' ); + } + + /** + * Get a list of the labels linked to a menu item, with links to manage them. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function list_admin_labels( $post_id = 0 ) { + $post = get_post( $post_id ); + $labels = get_the_terms( $post->ID, self::MENU_ITEM_LABEL_TAX ); + if ( ! empty( $labels ) ) { + $out = array(); + foreach ( $labels as $label ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach + $out[] = sprintf( + '%s', + esc_url( + add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'taxonomy' => self::MENU_ITEM_LABEL_TAX, + 'term' => $label->slug, + ), + 'edit.php' + ) + ), + esc_html( + sanitize_term_field( 'name', $label->name, $label->term_id, self::MENU_ITEM_LABEL_TAX, 'display' ) + ) + ); + } + + echo implode( _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), $out ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we build $out ourselves and escape things there. + } else { + esc_html_e( 'No Labels', 'jetpack-classic-theme-helper' ); + } + } + + /** + * Update post meta with the price defined in meta box. + * + * @param int $post_id Post ID. + * @param string $price Price. + * + * @return int|bool + */ + public function set_price( $post_id = 0, $price = '' ) { + return update_post_meta( $post_id, 'nova_price', $price ); + } + + /** + * Get the price of a menu item. + * + * @param int $post_id Post ID. + * + * @return bool|string + */ + public function get_price( $post_id = 0 ) { + return get_post_meta( $post_id, 'nova_price', true ); + } + + /** + * Echo the price of a menu item. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function display_price( $post_id = 0 ) { + echo esc_html( $this->get_price( $post_id ) ); + } + + /* Menu Item Loop Markup */ + + /** + * Get markup for a menu item. + * Note: Does not support nested loops. + * + * @param null|string $field The field to get the value for. + * + * @return array + */ + public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->menu_item_loop_markup; + } + + /** + * Sets up the loop markup. + * Attached to the 'template_include' *filter*, + * which fires only during a real blog view (not in admin, feeds, etc.) + * + * @param string $template Template File. + * + * @return string Template File. VERY Important. + */ + public function setup_menu_item_loop_markup__in_filter( $template ) { + add_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); + + return $template; + } + + /** + * If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku + * Attached to the 'loop_start' action. + * + * @param WP_Query $query Post query. + * + * @return void + */ + public function start_menu_item_loop( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } + + $this->menu_item_loop_last_term_id = false; + $this->menu_item_loop_current_term = false; + + add_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); + add_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); + } + + /** + * Outputs the Menu Item Loop Markup + * Attached to the 'the_post' action. + * + * @param WP_Post $post Post object. + * + * @return void + */ + public function menu_item_loop_each_post( $post ) { + $this->menu_item_loop_current_term = $this->get_menu_item_menu_leaf( $post->ID ); + + if ( + false === $this->menu_item_loop_current_term + || null === $this->menu_item_loop_current_term + || is_wp_error( $this->menu_item_loop_current_term ) + ) { + return; + } + + if ( false === $this->menu_item_loop_last_term_id ) { + // We're at the very beginning of the loop + + $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section + $this->menu_item_loop_header(); // Output the menu's header + } elseif ( + is_object( $this->menu_item_loop_current_term ) && + $this->menu_item_loop_last_term_id !== $this->menu_item_loop_current_term->term_id + ) { + // We're not at the very beginning but still need to start a new menu section. End the previous menu section first. + + $this->menu_item_loop_close_element( 'menu' ); // End the previous menu section + $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section + $this->menu_item_loop_header(); // Output the menu's header + } + if ( is_object( $this->menu_item_loop_current_term ) ) { + + $this->menu_item_loop_last_term_id = $this->menu_item_loop_current_term->term_id; + } + } + + /** + * If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku + * Attached to the 'loop_end' action. + * + * @param WP_Query $query Post query. + * + * @return void + */ + public function stop_menu_item_loop( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } + + remove_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); + remove_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); + remove_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); + + $this->menu_item_loop_close_element( 'menu' ); // End the last menu section + } + + /** + * Outputs the Menu Group Header + * + * @return void + */ + public function menu_item_loop_header() { + $this->menu_item_loop_open_element( 'menu_header' ); + $this->menu_item_loop_open_element( 'menu_title' ); + echo esc_html( $this->menu_item_loop_current_term->name ); // @todo tax filter + $this->menu_item_loop_close_element( 'menu_title' ); + if ( $this->menu_item_loop_current_term->description ) : + $this->menu_item_loop_open_element( 'menu_description' ); + echo esc_html( $this->menu_item_loop_current_term->description ); // @todo kses, tax filter + $this->menu_item_loop_close_element( 'menu_description' ); + endif; + $this->menu_item_loop_close_element( 'menu_header' ); + } + + /** + * Outputs a Menu Item Markup element opening tag + * + * @param string $field - Menu Item Markup settings field. + * + * @return void + */ + public function menu_item_loop_open_element( $field ) { + $markup = $this->get_menu_item_loop_markup(); + /** + * Filter a menu item's element opening tag. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu item's element opening tag. + * @param string $field Menu Item Markup settings field. + * @param array $markup Array of markup elements for the menu item. + * @param false|object $term Taxonomy term for current menu item. + */ + echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- it's escaped in menu_item_loop_class. + 'jetpack_nova_menu_item_loop_open_element', + '<' . tag_escape( $markup[ "{$field}_tag" ] ) . $this->menu_item_loop_class( $markup[ "{$field}_class" ] ) . ">\n", + $field, + $markup, + $this->menu_item_loop_current_term + ); + } + + /** + * Outputs a Menu Item Markup element closing tag + * + * @param string $field - Menu Item Markup settings field. + * + * @return void + */ + public function menu_item_loop_close_element( $field ) { + $markup = $this->get_menu_item_loop_markup(); + /** + * Filter a menu item's element closing tag. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu item's element closing tag. + * @param string $field Menu Item Markup settings field. + * @param array $markup Array of markup elements for the menu item. + * @param false|object $term Taxonomy term for current menu item. + */ + echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tag_escape is used. + 'jetpack_nova_menu_item_loop_close_element', + '\n", + $field, + $markup, + $this->menu_item_loop_current_term + ); + } + + /** + * Returns a Menu Item Markup element's class attribute. + * + * @param string $class Class name. + * + * @return string HTML class attribute with leading whitespace. + */ + public function menu_item_loop_class( $class ) { + if ( ! $class ) { + return ''; + } + + /** + * Filter a menu Item Markup element's class attribute. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu Item Markup element's class attribute. + * @param string $class Menu Item Class name. + * @param false|object $term Taxonomy term for current menu item. + */ + return apply_filters( + 'jetpack_nova_menu_item_loop_class', + ' class="' . esc_attr( $class ) . '"', + $class, + $this->menu_item_loop_current_term + ); + } + } + +} diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/edit-items.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/edit-items.css new file mode 100644 index 0000000000000..85fbbe96ef231 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/edit-items.css @@ -0,0 +1,24 @@ +.widefat .menu-label-row td { + border-bottom-width: 1px; +} +.widefat .menu-label-row td h3 { + padding-left: 30px; +} +.widefat .menu-order-value { + width: 2.5em; + text-align: center; +} +.widefat .menu-label-row, .widefat .menu-label-row td { + background-color: #d6d6d6; + color: #111; + border: 0 none; +} +.ui-sortable .type-nova_menu_item { + cursor: move; +} +.tablenav .button-reorder { + margin-top: 4px; +} +.tablenav .view-switch a, .tablenav div.tablenav-pages { + display: none; +} \ No newline at end of file diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/many-items.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/many-items.css new file mode 100644 index 0000000000000..c99324301743d --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/many-items.css @@ -0,0 +1,14 @@ +.many-items-table th, .many-items-table td { + width: 25%; +} + +.many-items-table input, .many-items-table textarea { + width: 100%; +} + +.many-items-table input[type=file] { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/nova-font.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova-font.css new file mode 100644 index 0000000000000..ac1b90679e656 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova-font.css @@ -0,0 +1,30 @@ +@font-face { + font-family: 'nova-font'; + src: url('../fonts/nova.eot'); +} +@font-face { + font-family: 'nova-font'; + src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg5lAuAAAAC8AAAAYGNtYXDL9xqaAAABHAAAADxnYXNwAAAAEAAAAVgAAAAIZ2x5Zrlfj0YAAAFgAAABrGhlYWQAW+atAAADDAAAADZoaGVhB2ED4AAAA0QAAAAkaG10eAXcAGQAAANoAAAADGxvY2EACgDWAAADdAAAAAhtYXhwAAgAkQAAA3wAAAAgbmFtZXvEneAAAAOcAAABHnBvc3QAAwAAAAAEvAAAACAAAwPoAZAABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAACDmAwOp/8L/wgOpAD4AAAAAAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEACgAAAAGAAQAAQACACDmA///AAAAIOYD////4Rn/AAEAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAQAZAAyA7YDhAAoAEUAfQCOAAABMhY6ATMyPgI/ASMHJz8BDwEnNycHDgMHFgYWBhcHMgYWIjMXNwUHDgMHHgMXHgMzMj4CPwEnKgMjFyc3Ni4CJy4DIyIOAgcOAxceAxceAz8BAR4DMzI+Ajc+Ayc2LgIvAQEmPgI3PgEeARceAxcnApQCBAQEAg4XFxQKtzGXH2oaM3UhlwHUCg0KBAEBAQIBAhQBAQEBAXIN/t3oCwwLAwEBAwsMCwgVFhkMDhgXEwuxcgICAwEC94wFBQMUIRkRLS0xFQsVFxULCQ8GAgQDDxMaDxUuNDIaDgEwBhAQFQkMExIQCAcMBgUBAQUGDAfm/i8CAwIHAhApLy0VCRMMCwLrAfUBBAoNCtWWIHYyGWsglDS5CRQWGQ0CBAQEAw8BAWkOKsIJFBYZDQ0YFxQJCg0KBAQKDQrWngp+DxczNTUXEx4VDAMHDAoKGh4iExMnJSMPFB4SBQQD/l8HCgYEBAcLCAcQEhQKCxMSEAjPAV8BBgcHBA4KCRgTCxgWFQlaAAAAAAEAAAABAAAD2anvXw889QALA+gAAAAAzsPRIgAAAADOw9EiAAAAAAO2A4QAAAAIAAIAAAAAAAAAAQAAA6n/wgAAA+gAAAAyA7YAAQAAAAAAAAAAAAAAAAAAAAMAAAAAAfQAAAPoAGQAAAAAAAoA1gABAAAAAwCPAAQAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEACAAAAAEAAAAAAAIADgAyAAEAAAAAAAMACAAeAAEAAAAAAAQACABAAAEAAAAAAAUAFgAIAAEAAAAAAAYABAAmAAEAAAAAAAoAKABIAAMAAQQJAAEACAAAAAMAAQQJAAIADgAyAAMAAQQJAAMACAAeAAMAAQQJAAQACABAAAMAAQQJAAUAFgAIAAMAAQQJAAYACAAqAAMAAQQJAAoAKABIAG4AbwB2AGEAVgBlAHIAcwBpAG8AbgAgADAALgAwAG4AbwB2AGFub3ZhAG4AbwB2AGEAUgBlAGcAdQBsAGEAcgBuAG8AdgBhAEcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype'), + url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUoAAsAAAAABNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDmUC4GNtYXAAAAFoAAAAPAAAADzL9xqaZ2FzcAAAAaQAAAAIAAAACAAAABBnbHlmAAABrAAAAawAAAGsuV+PRmhlYWQAAANYAAAANgAAADYAW+ataGhlYQAAA5AAAAAkAAAAJAdhA+BobXR4AAADtAAAAAwAAAAMBdwAZGxvY2EAAAPAAAAACAAAAAgACgDWbWF4cAAAA8gAAAAgAAAAIAAIAJFuYW1lAAAD6AAAAR4AAAEee8Sd4HBvc3QAAAUIAAAAIAAAACAAAwAAAAMD6AGQAAUAAAKKArwAAACMAooCvAAAAeAAMQECAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg5gMDqf/C/8IDqQA+AAAAAAAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAAoAAAABgAEAAEAAgAg5gP//wAAACDmA////+EZ/wABAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAAEAGQAMgO2A4QAKABFAH0AjgAAATIWOgEzMj4CPwEjByc/AQ8BJzcnBw4DBxYGFgYXBzIGFiIzFzcFBw4DBx4DFx4DMzI+Aj8BJyoDIxcnNzYuAicuAyMiDgIHDgMXHgMXHgM/AQEeAzMyPgI3PgMnNi4CLwEBJj4CNz4BHgEXHgMXJwKUAgQEBAIOFxcUCrcxlx9qGjN1IZcB1AoNCgQBAQECAQIUAQEBAQFyDf7d6AsMCwMBAQMLDAsIFRYZDA4YFxMLsXICAgMBAveMBQUDFCEZES0tMRULFRcVCwkPBgIEAw8TGg8VLjQyGg4BMAYQEBUJDBMSEAgHDAYFAQEFBgwH5v4vAgMCBwIQKS8tFQkTDAsC6wH1AQQKDQrVliB2MhlrIJQ0uQkUFhkNAgQEBAMPAQFpDirCCRQWGQ0NGBcUCQoNCgQECg0K1p4Kfg8XMzU1FxMeFQwDBwwKChoeIhMTJyUjDxQeEgUEA/5fBwoGBAQHCwgHEBIUCgsTEhAIzwFfAQYHBwQOCgkYEwsYFhUJWgAAAAABAAAAAQAAA9mp718PPPUACwPoAAAAAM7D0SIAAAAAzsPRIgAAAAADtgOEAAAACAACAAAAAAAAAAEAAAOp/8IAAAPoAAAAMgO2AAEAAAAAAAAAAAAAAAAAAAADAAAAAAH0AAAD6ABkAAAAAAAKANYAAQAAAAMAjwAEAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAgAAAABAAAAAAACAA4AMgABAAAAAAADAAgAHgABAAAAAAAEAAgAQAABAAAAAAAFABYACAABAAAAAAAGAAQAJgABAAAAAAAKACgASAADAAEECQABAAgAAAADAAEECQACAA4AMgADAAEECQADAAgAHgADAAEECQAEAAgAQAADAAEECQAFABYACAADAAEECQAGAAgAKgADAAEECQAKACgASABuAG8AdgBhAFYAZQByAHMAaQBvAG4AIAAwAC4AMABuAG8AdgBhbm92YQBuAG8AdgBhAFIAZQBnAHUAbABhAHIAbgBvAHYAYQBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff'); + font-weight: normal; + font-style: normal; +} + +#menu-posts-nova_menu_item:before, +#dashboard_right_now .nova-menu-count a:before, +#dashboard_right_now .nova-menu-count span:before { + font-family: 'nova-font'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +#dashboard_right_now .nova-menu-count a:before, #dashboard_right_now .nova-menu-count span:before { + content: '\e603'; +} \ No newline at end of file diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/nova.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova.css new file mode 100644 index 0000000000000..616433a3c47a0 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova.css @@ -0,0 +1,110 @@ +/* edit-items.css +-------------------------------------------------------------- */ + +.widefat .menu-label-row td { + border-bottom-width: 1px; +} +.widefat .menu-label-row td h3 { + padding-left: 30px; +} +.widefat .menu-label-row td h3 .edit-nova-section { + font-size: .8em; + font-weight: normal; + margin-left: 5px; +} +.widefat .menu-order-value { + width: 2.5em; + text-align: center; +} +.widefat .menu-label-row, .widefat .menu-label-row td { + background-color: #f0f0f1; + color: #111; + border: 0 none; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05); +} +.ui-sortable .type-nova_menu_item { + cursor: move; + background-color: #fff; +} +.ui-sortable .type-nova_menu_item:nth-child(even) { + background-color: #f6f7f7; +} +.ui-sortable .type-nova_menu_item.ui-sortable-helper { + -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1); + box-shadow: 0 0 1px rgba(0, 0, 0, 0.1); +} +.tablenav .button-reorder { + margin-top: 4px; +} +.tablenav .view-switch a, .tablenav div.tablenav-pages { + display: none; +} + + +/* many-items.css +-------------------------------------------------------------- */ + +.many-items-table th, .many-items-table td { + width: 30%; +} + +.many-items-table th.nova-price, .many-items-table td.nova-price { + width: 10%; +} + +.many-items-table input, .many-items-table textarea { + width: 100%; +} + +.many-items-table input[type=file] { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} + + +/* new +-------------------------------------------------------------- */ + +#the-list tr td:nth-of-type(2) { + padding-top: 15px; +} + +.nova-move-menu-up:before, +.nova-move-menu-down:before { + margin-right: 5px; + font: normal 10px/1 'dashicons' !important; + speak: none; +} + +.nova-move-menu-up:before { + content: "\f342"; +} + +.nova-move-menu-down:before { + content: "\f346"; +} + +.dashicon:before { + font: normal 20px/1 'dashicons'; + speak: none; + top: 5px; + display: inline-block; + position: relative; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: none !important; + vertical-align: top; +} + +.dashicon-plus:before { + content: "\f132"; +} + +.dashicon-edit:before { + margin: 2px 5px 0 10px; + content: "\f327"; + font-size: 10px; +} diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/js/many-items.js b/projects/packages/classic-theme-helper/src/custom-post-types/js/many-items.js new file mode 100644 index 0000000000000..3cbc17466d79c --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/js/many-items.js @@ -0,0 +1,139 @@ +( function () { + let menuSelector, nonceInput; + const initializedTables = new Set(); + + const methods = { + init: function ( table ) { + let tbody = table.lastElementChild; + while ( tbody && tbody.tagName !== 'TBODY' ) { + tbody = tbody.previousElementSibling; + } + const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true ); + + table.dataset.form = table.closest( 'form' ); + table.dataset.tbody = tbody; + table.dataset.row = row; + table.dataset.currentRow = row; + + menuSelector = document.getElementById( 'nova-menu-tax' ); + nonceInput = document.getElementById( '_wpnonce' ); + + table.addEventListener( 'keypress', function ( event ) { + if ( event.which !== 13 ) return; + + event.preventDefault(); + if ( typeof FormData === 'function' ) { + methods.submitRow.call( table ); + } + methods.addRow.call( table ); + } ); + + table.addEventListener( 'focusin', function ( event ) { + if ( event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA' ) { + table.dataset.currentRow = event.target.closest( 'tr' ); + } + } ); + + initializedTables.add( table ); + return table; + }, + + destroy: function ( table ) { + table.removeEventListener( 'keypress', methods.keypressHandler ); + table.removeEventListener( 'focusin', methods.focusinHandler ); + initializedTables.delete( table ); + return table; + }, + + submitRow: function ( table ) { + const submittedRow = table.dataset.currentRow; + const currentInputs = submittedRow.querySelectorAll( 'input, textarea, select' ); + const form = document.querySelector( table.dataset.form ); + const allInputs = Array.from( form.querySelectorAll( 'input, textarea, select' ) ); + + currentInputs.forEach( input => ( input.disabled = true ) ); + allInputs + .filter( input => ! currentInputs.includes( input ) ) + .forEach( input => ( input.disabled = true ) ); + + const partialFormData = new FormData( form ); + partialFormData.append( 'ajax', '1' ); + partialFormData.append( 'nova_menu_tax', menuSelector.value ); + partialFormData.append( '_wpnonce', nonceInput.value ); + + fetch( '', { + method: 'POST', + body: partialFormData, + } ) + .then( response => response.text() ) + .then( responseText => { + submittedRow.innerHTML = responseText; + } ); + + allInputs.forEach( input => ( input.disabled = false ) ); + + return table; + }, + + addRow: function ( table ) { + const row = table.dataset.row.cloneNode( true ); + + const tbody = table.dataset.tbody; + tbody.appendChild( row ); + + const firstInput = row.querySelector( 'input, textarea, select' ); + if ( firstInput ) firstInput.focus(); + + return table; + }, + + clickAddRow: function ( table ) { + let tbody = table.lastElementChild; + + while ( tbody && tbody.tagName !== 'TBODY' ) { + tbody = tbody.previousElementSibling; + } + const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true ); + + row.querySelectorAll( 'input, textarea' ).forEach( input => { + input.value = ''; + } ); + + tbody.appendChild( row ); + }, + }; + + const observeTableRemoval = function () { + const observer = new MutationObserver( mutations => { + mutations.forEach( mutation => { + mutation.removedNodes.forEach( node => { + if ( node.matches && node.matches( '.many-items-table' ) ) { + methods.destroy( node ); + } + } ); + } ); + } ); + + observer.observe( document.body, { childList: true, subtree: true } ); + }; + + // Initialization for many-items-table + document.addEventListener( 'focusin', event => { + const table = event.target.closest( '.many-items-table' ); + if ( table && ! initializedTables.has( table ) ) { + methods.init( table ); + } + } ); + + document.addEventListener( 'click', event => { + if ( event.target.matches( 'a.nova-new-row' ) ) { + const table = event.target.closest( '.many-items-table' ); + if ( table ) { + event.preventDefault(); + methods.clickAddRow( table ); + } + } + } ); + + observeTableRemoval(); +} )(); diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/js/menu-checkboxes.js b/projects/packages/classic-theme-helper/src/custom-post-types/js/menu-checkboxes.js new file mode 100644 index 0000000000000..ed3418d76ccdf --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/js/menu-checkboxes.js @@ -0,0 +1,75 @@ +( function () { + const NovaCheckBoxes = { + inputs: null, + popInputs: null, + + initialize: function () { + // Get all checkboxes in the "nova_menuchecklist-pop" + NovaCheckBoxes.popInputs = document.querySelectorAll( + '#nova_menuchecklist-pop input[type="checkbox"]' + ); + + // Get all checkboxes in the "nova_menuchecklist" and add event listeners + NovaCheckBoxes.inputs = document.querySelectorAll( + '#nova_menuchecklist input[type="checkbox"]' + ); + NovaCheckBoxes.inputs.forEach( input => { + input.addEventListener( 'change', NovaCheckBoxes.checkOne ); + input.addEventListener( 'change', NovaCheckBoxes.syncPop ); + } ); + + // If no checkboxes are checked, check the first one + if ( ! NovaCheckBoxes.isChecked() ) { + NovaCheckBoxes.checkFirst(); + } + + // Sync the state of the "pop" inputs + NovaCheckBoxes.syncPop(); + }, + + syncPop: function () { + NovaCheckBoxes.popInputs.forEach( popInput => { + const linkedInput = document.querySelector( `#in-nova_menu-${ popInput.value }` ); + popInput.checked = linkedInput ? linkedInput.checked : false; + } ); + }, + + isChecked: function () { + return Array.from( NovaCheckBoxes.inputs ).some( input => input.checked ); + }, + + checkFirst: function () { + const firstInput = NovaCheckBoxes.inputs[ 0 ]; + if ( firstInput ) { + firstInput.checked = true; + } + }, + + checkOne: function () { + const currentInput = this; + + // If the current checkbox is checked, uncheck all other checkboxes + if ( currentInput.checked ) { + NovaCheckBoxes.inputs.forEach( input => { + if ( input !== currentInput ) { + input.checked = false; + } + } ); + return; + } + const checklist = document.querySelector( '#nova_menuchecklist' ); + + // If at least one checkbox is still checked, uncheck the current one + if ( checklist.querySelectorAll( 'input[type="checkbox"]:checked' ).length > 0 ) { + currentInput.checked = false; + return; + } + + // Otherwise, check the first checkbox + NovaCheckBoxes.checkFirst(); + }, + }; + + // Initialize when the DOM is fully loaded + document.addEventListener( 'DOMContentLoaded', NovaCheckBoxes.initialize ); +} )(); diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/js/nova-drag-drop.js b/projects/packages/classic-theme-helper/src/custom-post-types/js/nova-drag-drop.js new file mode 100644 index 0000000000000..8629bbe092803 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/js/nova-drag-drop.js @@ -0,0 +1,85 @@ +/* eslint-disable no-undef */ +/* global _novaDragDrop */ + +( function ( $ ) { + let list; + + /** + * Initialize the drag and drop functionality. + */ + function init() { + list = $( '#the-list' ); + dragMenus(); + addNonce(); + addSubmitButton(); + changeToPost(); + } + + /** + * Allow the menu items to be dragged. + */ + function dragMenus() { + list.sortable( { + cancel: '.no-items, .inline-edit-row', + stop: function ( event, ui ) { + if ( ui.item.is( ':first-child' ) ) { + return list.sortable( 'cancel' ); + } + // + reOrder(); + }, + } ); + } + + /** + * Allow the menu items to be reordered. + */ + function reOrder() { + list.find( '.menu-label-row' ).each( function () { + const term_id = $( this ).data( 'term_id' ); + $( this ) + .nextUntil( '.menu-label-row' ) + .each( function ( i ) { + const row = $( this ); + row.find( '.menu-order-value' ).val( i ); + row.find( '.nova-menu-term' ).val( term_id ); + } ); + } ); + } + + /** + * Ensure the submit button is added to the page. + */ + function addSubmitButton() { + $( '.tablenav' ).prepend( + '' + ); + } + + /** + * Add the nonce to the form. + */ + function addNonce() { + $( '#posts-filter' ).append( + '' + ); + } + + /** + * Change the form method to POST. + */ + function changeToPost() { + $( '#posts-filter' ).attr( 'method', 'post' ); + } + + // do it + $( document ).ready( init ); +} )( jQuery ); diff --git a/projects/packages/jetpack-mu-wpcom/.phan/baseline.php b/projects/packages/jetpack-mu-wpcom/.phan/baseline.php index f8dea226fcd09..6865fcfd0af51 100644 --- a/projects/packages/jetpack-mu-wpcom/.phan/baseline.php +++ b/projects/packages/jetpack-mu-wpcom/.phan/baseline.php @@ -9,46 +9,31 @@ */ return [ // # Issue statistics: - // PhanTypeMismatchArgument : 35+ occurrences - // PhanPluginDuplicateConditionalNullCoalescing : 20+ occurrences - // PhanUndeclaredClassMethod : 20+ occurrences + // PhanTypeMismatchArgument : 15+ occurrences // PhanTypeMismatchArgumentProbablyReal : 15+ occurrences - // PhanTypeMismatchReturnProbablyReal : 10+ occurrences - // PhanUndeclaredFunction : 10+ occurrences - // PhanTypeMismatchReturn : 8 occurrences - // PhanUndeclaredConstant : 7 occurrences - // PhanTypeArraySuspiciousNullable : 6 occurrences + // PhanPluginDuplicateConditionalNullCoalescing : 8 occurrences + // PhanTypeMismatchReturn : 6 occurrences + // PhanUndeclaredClassMethod : 6 occurrences // PhanNoopNew : 5 occurrences - // PhanParamTooMany : 4 occurrences - // PhanUnextractableAnnotationSuffix : 4 occurrences - // PhanDeprecatedProperty : 3 occurrences - // PhanPluginDuplicateExpressionAssignmentOperation : 3 occurrences + // PhanTypeMismatchArgumentInternal : 4 occurrences + // PhanTypeMismatchReturnProbablyReal : 4 occurrences // PhanTypePossiblyInvalidDimOffset : 3 occurrences - // PhanTypeSuspiciousNonTraversableForeach : 3 occurrences - // PhanUndeclaredClassReference : 3 occurrences - // PhanUndeclaredGlobalVariable : 3 occurrences // PhanEmptyFQSENInCallable : 2 occurrences - // PhanPluginMixedKeyNoKey : 2 occurrences + // PhanParamTooMany : 2 occurrences // PhanTypeArraySuspicious : 2 occurrences - // PhanTypeMismatchArgumentInternal : 2 occurrences + // PhanTypeArraySuspiciousNullable : 2 occurrences // PhanTypeMismatchDefault : 2 occurrences // PhanTypeMissingReturn : 2 occurrences + // PhanUndeclaredFunction : 2 occurrences // PhanDeprecatedFunction : 1 occurrence // PhanImpossibleTypeComparison : 1 occurrence // PhanNonClassMethodCall : 1 occurrence // PhanNoopArrayAccess : 1 occurrence - // PhanPluginRedundantAssignment : 1 occurrence // PhanPluginSimplifyExpressionBool : 1 occurrence - // PhanPossiblyUndeclaredVariable : 1 occurrence // PhanRedefineFunction : 1 occurrence // PhanRedundantCondition : 1 occurrence // PhanTypeComparisonToArray : 1 occurrence - // PhanTypeInvalidLeftOperandOfBitwiseOp : 1 occurrence - // PhanTypeInvalidRightOperandOfBitwiseOp : 1 occurrence - // PhanTypeMismatchArgumentNullable : 1 occurrence - // PhanTypeMismatchArgumentNullableInternal : 1 occurrence // PhanTypeMismatchDimFetch : 1 occurrence - // PhanTypeMismatchProperty : 1 occurrence // PhanTypeMismatchReturnNullable : 1 occurrence // PhanTypeNonVarPassByRef : 1 occurrence // PhanTypeVoidArgument : 1 occurrence @@ -68,14 +53,6 @@ 'src/features/launchpad/launchpad.php' => ['PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchArgument', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal'], 'src/features/marketplace-products-updater/class-marketplace-products-updater.php' => ['PhanTypeMismatchDimFetch', 'PhanTypeMismatchReturn'], 'src/features/media/heif-support.php' => ['PhanPluginSimplifyExpressionBool'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php' => ['PhanParamTooMany', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPossiblyUndeclaredVariable', 'PhanTypeMismatchArgument', 'PhanTypeMismatchReturnProbablyReal'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/class-wp-rest-newspack-articles-controller.php' => ['PhanTypeArraySuspiciousNullable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php' => ['PhanTypeMismatchArgument', 'PhanUndeclaredGlobalVariable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/articles-list.php' => ['PhanUndeclaredGlobalVariable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/articles-loop.php' => ['PhanUndeclaredGlobalVariable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/view.php' => ['PhanPluginDuplicateExpressionAssignmentOperation', 'PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchArgumentNullableInternal', 'PhanTypeMismatchReturnProbablyReal', 'PhanUndeclaredClassMethod'], - 'src/features/newspack-blocks/synced-newspack-blocks/class-newspack-blocks-api.php' => ['PhanParamTooMany', 'PhanTypeMismatchArgument', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal', 'PhanUndeclaredClassMethod', 'PhanUndeclaredFunction', 'PhanUnextractableAnnotationSuffix'], - 'src/features/newspack-blocks/synced-newspack-blocks/class-newspack-blocks.php' => ['PhanDeprecatedProperty', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginDuplicateExpressionAssignmentOperation', 'PhanPluginMixedKeyNoKey', 'PhanPluginRedundantAssignment', 'PhanTypeInvalidLeftOperandOfBitwiseOp', 'PhanTypeInvalidRightOperandOfBitwiseOp', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchProperty', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal', 'PhanTypeSuspiciousNonTraversableForeach', 'PhanUndeclaredClassMethod', 'PhanUndeclaredClassReference', 'PhanUndeclaredConstant', 'PhanUndeclaredFunction'], 'src/features/verbum-comments/class-verbum-comments.php' => ['PhanImpossibleTypeComparison', 'PhanNoopNew', 'PhanParamTooMany', 'PhanTypeMismatchArgumentProbablyReal', 'PhanUndeclaredFunction'], 'src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php' => ['PhanPluginDuplicateConditionalNullCoalescing'], 'src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-site-migration-migrate-guru-key.php' => ['PhanUndeclaredClassMethod'], diff --git a/projects/packages/jetpack-mu-wpcom/.phan/config.php b/projects/packages/jetpack-mu-wpcom/.phan/config.php index 8680d589c96a7..b69eda7cb1aef 100644 --- a/projects/packages/jetpack-mu-wpcom/.phan/config.php +++ b/projects/packages/jetpack-mu-wpcom/.phan/config.php @@ -32,6 +32,8 @@ // This file breaks analysis, Phan gets lost recursing in trying to figure out some types. // @todo Add type declarations so Phan won't have to do it itself. Or update to a modern less lib. 'src/features/custom-css/custom-css/preprocessors/lessc.inc.php', + // This is synced from the Newspack Blocks repo. + 'src/features/newspack-blocks/synced-newspack-blocks/', ), ) ); diff --git a/projects/packages/jetpack-mu-wpcom/.phpcsignore b/projects/packages/jetpack-mu-wpcom/.phpcsignore new file mode 100644 index 0000000000000..6a0ddf86aeb10 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/.phpcsignore @@ -0,0 +1 @@ +src/features/newspack-blocks/synced-newspack-blocks diff --git a/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh b/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh index 55fda6a5bac10..89a1cf6b9c348 100755 --- a/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh +++ b/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh @@ -14,7 +14,7 @@ fi # pick up value considering that the argument # has the --key=value shape. -key_value=$(echo ${1} | cut -d'=' -f 2) +key_value=$(echo "${1}" | cut -d'=' -f 2) # Set mode depending on first argument if [[ $1 =~ ^--release= ]] then @@ -44,7 +44,7 @@ then echo --branch=master echo "--nodemodules (to use defined in package.json)" echo "--path=/path/to/newspack-blocks" - echo --release=v2.0.0 + echo --release=v4.0.0 echo echo You can find the latest release ID on https://github.com/Automattic/newspack-blocks/releases/latest echo @@ -52,7 +52,6 @@ then fi TARGET=./src/features/newspack-blocks/synced-newspack-blocks -ENTRY=./src/features/newspack-blocks/index.php if [[ ( "$MODE" != "path" ) && ( "$MODE" != "npm" ) ]]; then @@ -62,20 +61,23 @@ then if [[ "$CURRENT_VERSION" == "$NAME" ]]; then echo "The current version $CURRENT_VERSION of the newspack-blocks is synced." - exit 0 + read -rp "Do you want to proceed anyway? (y/N): " proceed + if [[ ! "$proceed" =~ ^[Yy]$ ]]; then + exit 0 + fi fi fi # make a temp directory - TEMP_DIR=`mktemp -d` + TEMP_DIR=$(mktemp -d) CODE=$TEMP_DIR/code # download zip file - echo Downloading $MODE $NAME into $TEMP_DIR - (cd $TEMP_DIR && curl -L --fail -s -O $URL) + echo "Downloading $MODE $NAME into $TEMP_DIR" + (cd "$TEMP_DIR" && curl -L --fail -s -O "$URL") # handle download error - ZIPS=( $TEMP_DIR/*.zip ) + ZIPS=( "$TEMP_DIR"/*.zip ) ZIP=${ZIPS[0]} if [ ! -f "$ZIP" ]; then echo "Tried to download $URL" @@ -90,13 +92,13 @@ then fi # extract zip - echo Extracting into $CODE - mkdir -p $CODE - unzip -q $ZIP -d $CODE + echo "Extracting into $CODE" + mkdir -p "$CODE" + unzip -q "$ZIP" -d "$CODE" # find the main file and use its directory as the root of our source dir - MAIN_FILE=`find $CODE -name "newspack-blocks.php"` - CODE=`dirname $MAIN_FILE` + MAIN_FILE=$(find "$CODE" -name "newspack-blocks.php") + CODE=$(dirname "$MAIN_FILE") # handle unzip error if [ ! -f "$CODE/newspack-blocks.php" ]; then @@ -127,18 +129,22 @@ mkdir -p $TARGET/components mkdir -p $TARGET/shared mkdir -p $TARGET/types -# copy files and directories -NEW_VERSION=v`jq -r .version $CODE/package.json` +# Update Newspack Blocks version number in the package. +NEW_VERSION=v$(jq -r .version "$CODE"/package.json) echo "$NEW_VERSION" > $TARGET/version.txt -cp $CODE/includes/class-newspack-blocks-api.php $TARGET/ -cp $CODE/includes/class-newspack-blocks.php $TARGET/ -cp -R $CODE/src/blocks/homepage-articles $TARGET/blocks/ -cp -R $CODE/src/blocks/carousel $TARGET/blocks/ -cp -R $CODE/src/shared $TARGET/ -cp -R $CODE/src/components $TARGET/ +sed -E -i.bak "s|^define\( 'NEWSPACK_BLOCKS__VERSION', '.*' \);$|define( 'NEWSPACK_BLOCKS__VERSION', '$NEW_VERSION' );|" "$TARGET"/../index.php +rm "$TARGET"/../index.php.bak + +# copy files and directories +cp "$CODE"/includes/class-newspack-blocks-api.php $TARGET/ +cp "$CODE"/includes/class-newspack-blocks.php $TARGET/ +cp -R "$CODE"/src/blocks/homepage-articles $TARGET/blocks/ +cp -R "$CODE"/src/blocks/carousel $TARGET/blocks/ +cp -R "$CODE"/src/shared $TARGET/ +cp -R "$CODE"/src/components $TARGET/ # Get Typescript working by copying the main type defs over. -cp $CODE/src/types/index.d.ts $TARGET/types/ +cp "$CODE"/src/types/index.d.ts $TARGET/types/ # Function types need to be capitalized in our system. We only match " function" # beginning with a space to avoid matching it as a substring. (Not perfect, but # imperfections will be caught by CI with failing tsc, etc.) @@ -146,12 +152,61 @@ sed "${sedi[@]}" -e "s| function| Function|g" "$TARGET/types/index.d.ts" # Note: I would have used eslint-nibble, but it doesn't support autofixing via the CLI. echo "Changing JS textdomain to match jetpack-mu-wpcom..." -pnpm --package=eslint@8.57.0 dlx eslint --no-ignore --rule '"@wordpress/i18n-text-domain":["error",{"allowedTextDomain":"jetpack-mu-wpcom"}]' --fix $TARGET > /dev/null +BASE=$(cd "$(dirname "${BASH_SOURCE[0]}")"/../../../.. && pwd) +FULLTARGET="$PWD/$TARGET" + +# Add a temporary single-rule eslint.config.mjs file. +cat > "$TARGET/eslint.config.mjs" < ( { ...block, rules: {} } ) ), + // Enable just this one rule. + { + rules: { + "@wordpress/i18n-text-domain": [ "error", { allowedTextDomain: "jetpack-mu-wpcom" } ], + } + }, +]; +EOF +( cd "$BASE" && pnpm run lint-file --no-inline-config --no-ignore --fix "$FULLTARGET" ) +rm "$TARGET/eslint.config.mjs" echo "Changing JS translation function call to avoid bad minification..." pnpm --package=jscodeshift dlx jscodeshift -t ./bin/sync-newspack-blocks-formatter.js --extensions=js $TARGET +# Add temporary PHPCS config file. +PHPCSSTANDARDFILE="$TARGET/phpcs.tmp.xml" +cat > "$PHPCSSTANDARDFILE" < + + + + + + + + + + + + + + + + +EOF echo "Changing PHP textdomain to match jetpack-mu-wpcom..." -../../../vendor/bin/phpcbf --standard=./.phpcs.dir.xml --filter=../../../vendor/automattic/jetpack-phpcs-filter/src/PhpcsFilter.php --runtime-set jetpack-filter-no-ignore -q $TARGET - +"$BASE"/vendor/bin/phpcbf --standard="$PHPCSSTANDARDFILE" "$TARGET" +rm "$PHPCSSTANDARDFILE" + +# Add textdomain to block.json +echo "Adding textdomain to all block.json files..." +for block_json_file in "$TARGET"/blocks/*/block.json; do + TMPFILE=$(mktemp) + jq --tab '. += {"textdomain": "jetpack-mu-wpcom"}' "$block_json_file" > "$TMPFILE" + mv "$TMPFILE" "$block_json_file" +done echo Sync done. diff --git a/projects/packages/jetpack-mu-wpcom/changelog/add-set-default-category-quick-action b/projects/packages/jetpack-mu-wpcom/changelog/add-set-default-category-quick-action new file mode 100644 index 0000000000000..562cea91dca9a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/add-set-default-category-quick-action @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Post categories: Add quick action to change default category diff --git a/projects/packages/jetpack-mu-wpcom/changelog/renovate-playwright-monorepo b/projects/packages/jetpack-mu-wpcom/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/jetpack-mu-wpcom/changelog/update-synced_newspack_blocks b/projects/packages/jetpack-mu-wpcom/changelog/update-synced_newspack_blocks new file mode 100644 index 0000000000000..15206e680281d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/update-synced_newspack_blocks @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Newspack Blocks: Updated to version 4.5.2. diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json index 4123ddacdca47..8dad01437fe0a 100644 --- a/projects/packages/jetpack-mu-wpcom/package.json +++ b/projects/packages/jetpack-mu-wpcom/package.json @@ -33,7 +33,7 @@ "@babel/core": "7.26.0", "@babel/plugin-transform-react-jsx": "7.25.9", "@babel/preset-react": "7.25.9", - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "@types/node": "^20.4.2", "@types/react": "^18.2.28", "@types/react-dom": "18.3.1", diff --git a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php index 26cf4192d604a..89597cf7ce895 100644 --- a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php +++ b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php @@ -107,6 +107,7 @@ public static function load_features() { require_once __DIR__ . '/features/import-customizations/import-customizations.php'; require_once __DIR__ . '/features/marketplace-products-updater/class-marketplace-products-updater.php'; require_once __DIR__ . '/features/media/heif-support.php'; + require_once __DIR__ . '/features/post-categories/quick-actions.php'; require_once __DIR__ . '/features/site-editor-dashboard-link/site-editor-dashboard-link.php'; require_once __DIR__ . '/features/wpcom-admin-dashboard/wpcom-admin-dashboard.php'; require_once __DIR__ . '/features/wpcom-block-editor/class-jetpack-wpcom-block-editor.php'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md index a4cc12220990b..b712c4428c0cd 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md @@ -1,56 +1,56 @@ # Newspack Blocks -Some of the Newspack blocks were added to this repository in order to make it available to other parts of the FSE plugin, such as Starter Page Templates where these blocks will be used. +Some of the Newspack blocks were added to this repository so they would be available to other parts of the FSE plugin where these blocks will be used, such as Starter Page Templates. ## Block Posts Block This block allows you to list your posts in various layouts and filter them by criteria like category, tag or author. -It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there. +It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there as the `homepage-articles` block. -## Post Carousel Block +## Carousel Block -This block allows you to create a carousel of post's featured images and filter them by criteria like category, tag or author. +This block allows you to create a carousel of post featured images and filter them by criteria (e.g. category, tag or author). -It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there. +It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there as the `carousel` block. ## Structure ``` index.php — main entry file, registers the blocks on backend -blog-posts-block-editor.min.js — assets for the blog-posts-block editor -blog-posts-block-view.min.js — assets for the blog-posts-block rendered on frontend -carousel-block-editor.min.js — assets for the carousel-block editor -carousel-block-view.min.js — assets for the carousel-block rendered on frontend -synced-newspack-blocks/** — source code synced from the Newspack Blocks repository, not tracked in Jetpack git repo +blog-posts/ — assets for the blog-posts block frontend and editor +carousel/ — assets for the carousel block frontend and editor +synced-newspack-blocks/ — source code synced from the Newspack Blocks repository ``` -`blog-posts-block-editor.min.js`, `blog-posts-block-view.min.js`, `carousel-block-editor.min.js`, `carousel-block-view.min.js` and `index.php` are files written in order to bridge the parent plugin with the Newspack Blocks. It changes the block names to an `a8c/` namespace and does things like registering REST fields or styles and scripts. In these files we are free to do all those changes because they are not shared with Newspack and only live here in this repository. +Other than the `synced-newspack-blocks` directory, the above are files written in order to bridge the parent plugin with Newspack Blocks. They change the block names to an `a8c/` namespace and register REST fields, styles, and scripts. In these files we are free to make changes because they are not shared with Newspack and only live here in this repository. + ### Synchronizing the code -You can see that `synced-newspack-blocks` is being synced with the Newspack Blocks repository. Please make all improvements and additions on the Newspack side. Please don't make any direct changes to files in this directory as the next synchronization will overwrite them. Synced files are not being tracked in git and they are always downloaded fresh using the sync script explained further in this document. +The `synced-newspack-blocks` is synced with the Newspack Blocks repository. *Please make all improvements and additions upstream in the Newspack Blocks repo. Do not make any direct changes to files in this directory, as the next synchronization will overwrite them.* -Once your changes land on the Newspack side, coordinate with the team (over issues/PRs) to [make a new release](https://github.com/Automattic/newspack-blocks/releases) and once you have the release ID, you can pull the code into here. +Once your changes land in the Newspack Blocks repo, coordinate with the team (over issues/PRs) to [make a new release](https://github.com/Automattic/newspack-blocks/releases). Once you have the release ID (e.g. `v4.0.0`, you start a sync. -While being in `projects/packages/jetpack-mu-wpcom` directory, you can run: +While in the `projects/packages/jetpack-mu-wpcom` directory, run the following: ``` pnpm run sync:newspack-blocks --release= ``` -This will pull the code from the release and integrate it into this repository. Please: - 1. Review changes - 2. Keep the PHPCS config if still necessary - 3. Ensure [htmlentities uses ENT_COMPAT](https://github.com/Automattic/jetpack/pull/38873/commits/16f57e6f01b6eed98a19cd0299261ce5ac075b8e) - 4. Update the phan baseline with `jetpack phan --update-baseline packages/jetpack-mu-wpcom` - 4. Update `NEWSPACK_BLOCKS__VERSION` in [index.php](./index.php) - 5. Ensure that the blocks `block.json` has `"textdomain": "jetpack-mu-wpcom"` - 6. Commit. +This will pull the code from the release into this repository and perform the following tasks: +* Copies TypeScript types into place. +* Changes JS and PHP textdomain refs to `jetpack-mu-wpcom`. +* Adjusts JS translation function calls to avoid minification issues. +* Checks for potential places where `ENT_COMPAT` should be used. + +Once the script has completed: +1. Ensure the changes shown match the changes in the release. +2. Commit. ### Local development -Sometimes, probably, you will need to sync the NHA code straight in your local environment. It means you will get working on both projects at the same time. For this situation, you'd like to reference the code source through the `path` bin script argument. +Sometimes, probably, you will need to sync the code straight in your local environment. It means you will get working on both projects at the same time. For this situation, you'd like to reference the code source through the `path` bin script argument. ``` pnpm run sync:newspack-blocks --path=/Absolute/path/of/newspack-blocks/ diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php index 83bbe4855c5d9..f5e9e3b03bdbe 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php @@ -11,7 +11,7 @@ define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', Jetpack_Mu_Wpcom::BASE_DIR . 'build/' ); define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -define( 'NEWSPACK_BLOCKS__VERSION', '4.0.1' ); +define( 'NEWSPACK_BLOCKS__VERSION', 'v4.5.2' ); require_once __DIR__ . '/../../utils.php'; require_once __DIR__ . '/synced-newspack-blocks/class-newspack-blocks.php'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/.phpcs.dir.xml b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/.phpcs.dir.xml deleted file mode 100644 index c49f51983d12d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/.phpcs.dir.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js index 151b9a0000201..661d61244b2ae 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js @@ -14,8 +14,8 @@ const autoplayClassName = 'wp-block-newspack-blocks-carousel__autoplay-playing'; /** * A helper for IE11-compatible iteration over NodeList elements. * - * @param {object} nodeList - List of nodes to be iterated over. - * @param {Function} cb - Invoked for each iteratee. + * @param {Object} nodeList List of nodes to be iterated over. + * @param {Function} cb Invoked for each iteratee. */ function forEachNode( nodeList, cb ) { /** @@ -29,7 +29,7 @@ function forEachNode( nodeList, cb ) { /** * Modifies attributes on slide HTML to make it accessible. * - * @param {HTMLElement} slide - Slide DOM element + * @param {HTMLElement} slide Slide DOM element */ function activateSlide( slide ) { if ( slide ) { @@ -41,7 +41,7 @@ function activateSlide( slide ) { /** * Modifies attributes on slide HTML to make it accessible. * - * @param {HTMLElement} slide - Slide DOM element + * @param {HTMLElement} slide Slide DOM element */ function deactivateSlide( slide ) { if ( slide ) { @@ -54,15 +54,15 @@ function deactivateSlide( slide ) { * Creates a Swiper instance with predefined config used by the Articles * Carousel block in both front-end and editor. * - * @param {object} els - Swiper elements - * @param {Element} els.block - Block element - * @param {Element} els.container - Swiper container element - * @param {Element} els.next - Next button element - * @param {Element} els.prev - Previous button element - * @param {Element} els.play - Play button element - * @param {Element} els.pause - Pause button element - * @param {Element} els.pagination - Pagination element - * @param {Object} config - Swiper config + * @param {Object} els Swiper elements + * @param {Element} els.block Block element + * @param {Element} els.container Swiper container element + * @param {Element} els.next Next button element + * @param {Element} els.prev Previous button element + * @param {Element} els.play Play button element + * @param {Element} els.pause Pause button element + * @param {Element} els.pagination Pagination element + * @param {Object} config Swiper config * @return {Object} Swiper instance */ export default function createSwiper( els, config = {} ) { @@ -161,13 +161,13 @@ export default function createSwiper( els, config = {} ) { escapeHTML( `${ currentSlide.innerText }, ${ - alt - ? /* translators: the title of the image. */ sprintf( - __( 'Image: %s, ', 'jetpack-mu-wpcom' ), - alt - ) - : '' - } + alt + ? /* translators: the title of the image. */ sprintf( + __( 'Image: %s, ', 'jetpack-mu-wpcom' ), + alt + ) + : '' +} ${ slideInfo }` ), 'assertive' diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js index c4a066ae61f1f..62a738282263b 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js @@ -3,12 +3,17 @@ /** * External dependencies */ +import { isEqual } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; // eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { dateI18n, __experimentalGetSettings } from '@wordpress/date'; +import { Component, createRef, Fragment, RawHTML } from '@wordpress/element'; import { BaseControl, Button, @@ -20,20 +25,16 @@ import { Spinner, ToggleControl, } from '@wordpress/components'; -import { compose } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; -import { dateI18n, __experimentalGetSettings } from '@wordpress/date'; -import { Component, createRef, Fragment, RawHTML } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; -import { __ } from '@wordpress/i18n'; -import classnames from 'classnames'; -import { isEqual } from 'lodash'; /** * Internal dependencies */ -import { PostTypesPanel, PostStatusesPanel } from '../../components/editor-panels'; import QueryControls from '../../components/query-controls'; +import { PostTypesPanel, PostStatusesPanel } from '../../components/editor-panels'; +import createSwiper from './create-swiper'; import { formatAvatars, formatByline, @@ -43,7 +44,6 @@ import { } from '../../shared/js/utils'; // Use same posts store as Homepage Posts block. import { postsBlockSelector, postsBlockDispatch, shouldReflow } from '../homepage-articles/utils'; -import createSwiper from './create-swiper'; // Max number of slides that can be shown at once. const MAX_NUMBER_OF_SLIDES = 6; @@ -242,9 +242,7 @@ class Edit extends Component {
    { hasNoPosts && ( -
    - { __( 'Sorry, no posts were found.', 'jetpack-mu-wpcom' ) } -
    +
    { __( 'Sorry, no posts were found.', 'jetpack-mu-wpcom' ) }
    ) } { ( ! this.state.swiperInitialized || ! latestPosts ) && ( @@ -302,9 +300,7 @@ class Edit extends Component { ) } { showCategory && ( ! post.newspack_post_sponsors || - post.newspack_sponsors_show_categories ) && ( - { decodeEntities( post.newspack_category_info ) } - ) } + post.newspack_sponsors_show_categories ) && ( { decodeEntities( post.newspack_category_info ) } ) }
    ) } { showTitle && ( @@ -337,15 +333,14 @@ class Edit extends Component { { dateI18n( dateFormat, post.date ) } ) } - { ( showCaption || showCredit ) && - post.newspack_featured_image_caption && ( -
    - ) } + { ( showCaption || showCredit ) && post.newspack_featured_image_caption && ( +
    + ) }
    ) } @@ -433,10 +428,9 @@ class Edit extends Component { help={ 'cover' === imageFit ? __( - 'The image will fill the entire slide and will be cropped if necessary.', - 'jetpack-mu-wpcom' - ) - : __( + 'The image will fill the entire slide and will be cropped if necessary.', + 'jetpack-mu-wpcom' + ) : __( 'The image will be resized to fit inside the slide without being cropped.', 'jetpack-mu-wpcom', 0 @@ -470,10 +464,7 @@ class Edit extends Component { { setAttributes( { hideControls: _hideControls } ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js index 37b562d71a759..796eacfc27460 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js @@ -36,11 +36,7 @@ export const settings = { foreground: '#36f', }, category: 'newspack', - keywords: [ - __( 'posts', 'jetpack-mu-wpcom' ), - __( 'slideshow', 'jetpack-mu-wpcom' ), - __( 'carousel', 'jetpack-mu-wpcom' ), - ], + keywords: [ __( 'posts', 'jetpack-mu-wpcom' ), __( 'slideshow', 'jetpack-mu-wpcom' ), __( 'carousel', 'jetpack-mu-wpcom' ) ], description: __( 'A carousel of posts.', 'jetpack-mu-wpcom' ), attributes: { className: { diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php index 87000f1e301fd..3d32080ba2eb2 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php @@ -19,7 +19,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { // This will let the FSE plugin know we need CSS/JS now. do_action( 'newspack_blocks_render_post_carousel' ); - ++$newspack_blocks_carousel_id; + $newspack_blocks_carousel_id++; $autoplay = isset( $attributes['autoplay'] ) ? $attributes['autoplay'] : false; $delay = isset( $attributes['delay'] ) ? absint( $attributes['delay'] ) : 3; $authors = isset( $attributes['authors'] ) ? $attributes['authors'] : array(); @@ -47,10 +47,10 @@ function newspack_blocks_render_block_carousel( $attributes ) { $authors = Newspack_Blocks::prepare_authors(); $newspack_blocks_post_id[ $post_id ] = true; - $article_classes = array( + $article_classes = [ 'post-has-image', 'swiper-slide', - ); + ]; // Add classes based on the post's assigned categories and tags. $article_classes[] = Newspack_Blocks::get_term_classes( $post_id ); @@ -58,7 +58,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { // Get sponsors for this post. $sponsors = Newspack_Blocks::get_all_sponsors( $post_id ); - ++$counter; + $counter++; $has_featured_image = has_post_thumbnail(); $post_type = get_post_type(); $post_link = Newspack_Blocks::get_post_link( $post_id ); @@ -72,7 +72,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { $show_credit = $attributes['showCredit']; // Validate the value of the "image fit" attribute. - $image_fits = array( 'cover', 'contain' ); + $image_fits = [ 'cover', 'contain' ]; $image_fit = in_array( $attributes['imageFit'], $image_fits, true ) ? $attributes['imageFit'] : $image_fits[0]; ?> @@ -129,7 +129,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { ) } { RichText.isEmpty( sectionHeader ) ? ( @@ -206,11 +207,16 @@ class Edit extends Component< HomepageArticlesProps > { { post.meta.newspack_post_subtitle || '' } ) } - { showExcerpt && ( + { showExcerpt && ! showFullContent && ( { post.excerpt.rendered } ) } + { ! showExcerpt && showFullContent && ( + + { post.full_content } + + ) } { showReadMore && post.post_link && ( { readMoreLabel } @@ -280,7 +286,9 @@ class Edit extends Component< HomepageArticlesProps > { mobileStack, minHeight, moreButton, + infiniteScroll, showExcerpt, + showFullContent, showReadMore, readMoreLabel, excerptLength, @@ -434,11 +442,20 @@ class Edit extends Component< HomepageArticlesProps > { ) : ( ! specificMode && ( - setAttributes( { moreButton: ! moreButton } ) } - /> + <> + setAttributes( { moreButton: ! moreButton } ) } + /> + { moreButton && ( + setAttributes( { infiniteScroll: ! infiniteScroll } ) } + /> + ) } + ) ) } { setAttributes( { showExcerpt: ! showExcerpt } ) } + onChange={ () => { + setAttributes({ + showExcerpt: !showExcerpt, + showFullContent: showFullContent ? false : showFullContent + }) + } } /> { showExcerpt && ( - setAttributes( { excerptLength: value } ) } - min={ 10 } - max={ 100 } - /> + + setAttributes( { excerptLength: value } ) } + min={ 10 } + max={ 100 } + /> + ) } - setAttributes( { showReadMore: ! showReadMore } ) } - /> + + { + setAttributes({ + showFullContent: !showFullContent, + showExcerpt: showExcerpt ? false : showExcerpt + }) + } } + /> + + + setAttributes( { showReadMore: ! showReadMore } ) } + /> + { showReadMore && ( { yield put( { type: 'DISABLE_UI' } ); - // Ensure innerBlocks are populated for widget area blocks. - // See https://github.com/WordPress/gutenberg/issues/32607#issuecomment-890728216. - const blocks = getBlocks().map( block => { - const innerBlocks = select( 'core/block-editor' ).getBlocks( block.clientId ); - return { - ...block, - innerBlocks, - }; - } ); + const blocks = recursivelyGetBlocks( getBlocks ); const blockQueries = getBlockQueries( blocks, blockNames ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php index 2a0f2e109f2e0..eb74101785a04 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php @@ -7,7 +7,7 @@ */ call_user_func( - function ( $data ) { + function( $data ) { $attributes = apply_filters( 'newspack_blocks_homepage_posts_block_attributes', $data['attributes'] ); $authors = Newspack_Blocks::prepare_authors(); $classes = array(); @@ -42,14 +42,14 @@ function ( $data ) { // This global will be used by the newspack_blocks_filter_hpb_srcset filter. global $newspack_blocks_hpb_rendering_context; - $newspack_blocks_hpb_rendering_context = array( 'attrs' => $attributes ); + $newspack_blocks_hpb_rendering_context = [ 'attrs' => $attributes ]; // Disable lazy loading by using an arbitraty `loading` attribute other than `lazy`. // Empty string or `false` would still result in `lazy`. if ( $attributes['disableImageLazyLoad'] ) { $thumbnail_args['loading'] = 'none'; } - if ( $attributes['fetchPriority'] && in_array( $attributes['fetchPriority'], array( 'high', 'low', 'auto' ), true ) ) { + if ( $attributes['fetchPriority'] && in_array( $attributes['fetchPriority'], [ 'high', 'low', 'auto' ], true ) ) { $thumbnail_args['fetchpriority'] = $attributes['fetchPriority']; } @@ -147,9 +147,12 @@ class="" @@ -162,7 +165,7 @@ class="" - - ) } - - - - -