From 06e57f2c6035a2b47f92cbb11c15b1ada7ca5210 Mon Sep 17 00:00:00 2001 From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:23:29 +0530 Subject: [PATCH 01/51] Font Library: add progress-bar while uploading font assets (#57463) * Add progress bar when uploading the font file via site editor * Add vertical spacing and make progress bar center align With this commit, we have centered the progress bar and added some vertical spacing to the progress bar so it look as in center.Also while uploading we have just shown the progress bar and no text before and after, because while uploading it is good to show only progress bar. * Add more consistent style for progress bar Added the consistent style for progress bar and also change the text position before any notice after uploading the fonts in order to reduce the layout shifts. --- .../font-library-modal/local-fonts.js | 64 ++++++++++++------- .../font-library-modal/style.scss | 3 + 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js index 9612f8be52f5ee..4030dcfb69a77f 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js @@ -11,6 +11,7 @@ import { FormFileUpload, Notice, FlexItem, + privateApis as componentsPrivateApis, } from '@wordpress/components'; import { useContext, useState, useEffect } from '@wordpress/element'; @@ -23,10 +24,14 @@ import { Font } from '../../../../lib/lib-font.browser'; import makeFamiliesFromFaces from './utils/make-families-from-faces'; import { loadFontFaceInBrowser } from './utils'; import { getNoticeFromInstallResponse } from './utils/get-notice-from-response'; +import { unlock } from '../../../lock-unlock'; + +const { ProgressBar } = unlock( componentsPrivateApis ); function LocalFonts() { const { installFonts } = useContext( FontLibraryContext ); const [ notice, setNotice ] = useState( null ); + const [ isUploading, setIsUploading ] = useState( false ); const supportedFormats = ALLOWED_FILE_EXTENSIONS.slice( 0, -1 ) .map( ( extension ) => `.${ extension }` ) @@ -58,6 +63,7 @@ function LocalFonts() { */ const handleFilesUpload = ( files ) => { setNotice( null ); + setIsUploading( true ); const uniqueFilenames = new Set(); const selectedFiles = [ ...files ]; const allowedFiles = selectedFiles.filter( ( file ) => { @@ -150,6 +156,7 @@ function LocalFonts() { const response = await installFonts( fontFamilies ); const installNotice = getNoticeFromInstallResponse( response ); setNotice( installNotice ); + setIsUploading( false ); }; return ( @@ -157,31 +164,28 @@ function LocalFonts() { - `.${ ext }` - ).join( ',' ) } - multiple={ true } - onChange={ onFilesUpload } - render={ ( { openFileDialog } ) => ( - - ) } - /> - { notice && ( + { ! isUploading && ( + `.${ ext }` + ).join( ',' ) } + multiple={ true } + onChange={ onFilesUpload } + render={ ( { openFileDialog } ) => ( + + ) } + /> + ) } + { isUploading && ( - - - { notice.message } - +
+ +
) } @@ -194,6 +198,18 @@ function LocalFonts() { supportedFormats ) } + { ! isUploading && notice && ( + + + + { notice.message } + + + ) }
); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss index cf7de98d6fbbb1..d026563d3b73ea 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/style.scss +++ b/packages/edit-site/src/components/global-styles/font-library-modal/style.scss @@ -94,6 +94,9 @@ justify-content: center; height: 250px; width: 100%; +} + +button.font-library-modal__upload-area { background-color: #f0f0f0; } From 56e53dfe57db335401c2e2cdd1b8589742fd49a7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Mon, 8 Jan 2024 09:08:33 +0000 Subject: [PATCH 02/51] Fix Link UI displaying out of sync results (#57522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove updating suggestions state * Reset current request after completion and don’t fetch on focus --- .../src/components/url-input/index.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 1451397ce68e5f..947c39abfd0c7d 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -66,7 +66,6 @@ class URLInput extends Component { this.state = { suggestions: [], showSuggestions: false, - isUpdatingSuggestions: false, suggestionsValue: null, selectedSuggestion: null, suggestionsListboxId: '', @@ -102,11 +101,7 @@ class URLInput extends Component { } // Update suggestions when the value changes. - if ( - prevProps.value !== value && - ! this.props.disableSuggestions && - ! this.state.isUpdatingSuggestions - ) { + if ( prevProps.value !== value && ! this.props.disableSuggestions ) { if ( value?.length ) { // If the new value is not empty we need to update with suggestions for it. this.updateSuggestions( value ); @@ -183,7 +178,6 @@ class URLInput extends Component { } this.setState( { - isUpdatingSuggestions: true, selectedSuggestion: null, loading: true, } ); @@ -203,7 +197,6 @@ class URLInput extends Component { this.setState( { suggestions, - isUpdatingSuggestions: false, suggestionsValue: value, loading: false, showSuggestions: !! suggestions.length, @@ -235,9 +228,15 @@ class URLInput extends Component { } this.setState( { - isUpdatingSuggestions: false, loading: false, } ); + } ) + .finally( () => { + // If this is the current promise then reset the reference + // to allow for checking if a new request is made. + if ( this.suggestionsRequest === request ) { + this.suggestionsRequest = null; + } } ); // Note that this assignment is handled *before* the async search request @@ -255,11 +254,12 @@ class URLInput extends Component { // When opening the link editor, if there's a value present, we want to load the suggestions pane with the results for this input search value // Don't re-run the suggestions on focus if there are already suggestions present (prevents searching again when tabbing between the input and buttons) + // or there is already a request in progress. if ( value && ! disableSuggestions && - ! this.state.isUpdatingSuggestions && - ! ( suggestions && suggestions.length ) + ! ( suggestions && suggestions.length ) && + this.suggestionsRequest === null ) { // Ensure the suggestions are updated with the current input value. this.updateSuggestions( value ); From f57bcc8b905bf4281726692e44476000bdc1cb89 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 8 Jan 2024 11:35:04 +0100 Subject: [PATCH 03/51] Editor: Add the show most used blocks preference to the site editor (#57637) --- .../edit-post/src/components/layout/index.js | 7 +------ .../src/components/preferences-modal/index.js | 1 + .../src/components/preferences-modal/index.js | 18 ++++++++++++++++++ .../src/components/inserter-sidebar/index.js | 7 +++++-- .../convert-editor-settings.js | 1 + 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 357819313ab1d8..3a53f81e4faf89 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -160,7 +160,6 @@ function Layout() { isDistractionFree, showBlockBreadcrumbs, showMetaBoxes, - showMostUsedBlocks, documentLabel, hasHistory, } = useSelect( ( select ) => { @@ -193,8 +192,6 @@ function Layout() { showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), showBlockBreadcrumbs: get( 'core', 'showBlockBreadcrumbs' ), - showMostUsedBlocks: - select( editPostStore ).isFeatureActive( 'mostUsedBlocks' ), // translators: Default label for the Document in the Block Breadcrumb. documentLabel: postTypeLabel || _x( 'Document', 'noun' ), hasBlockSelected: @@ -263,9 +260,7 @@ function Layout() { const secondarySidebar = () => { if ( mode === 'visual' && isInserterOpened ) { - return ( - - ); + return ; } if ( mode === 'visual' && isListViewOpened ) { return ; diff --git a/packages/edit-post/src/components/preferences-modal/index.js b/packages/edit-post/src/components/preferences-modal/index.js index 7ce98c1720d3dd..a422e4d0a207a2 100644 --- a/packages/edit-post/src/components/preferences-modal/index.js +++ b/packages/edit-post/src/components/preferences-modal/index.js @@ -263,6 +263,7 @@ export default function EditPostPreferencesModal() { <> ), }, + { + name: 'blocks', + tabLabel: __( 'Blocks' ), + content: ( + <> + + + + + ), + }, ]; if ( ! isModalActive ) { return null; diff --git a/packages/editor/src/components/inserter-sidebar/index.js b/packages/editor/src/components/inserter-sidebar/index.js index 3c9737f5e59e54..7db4335309935a 100644 --- a/packages/editor/src/components/inserter-sidebar/index.js +++ b/packages/editor/src/components/inserter-sidebar/index.js @@ -11,6 +11,7 @@ import { } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { useEffect, useRef } from '@wordpress/element'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies @@ -18,11 +19,13 @@ import { useEffect, useRef } from '@wordpress/element'; import { unlock } from '../../lock-unlock'; import { store as editorStore } from '../../store'; -export default function InserterSidebar( { showMostUsedBlocks } ) { - const { insertionPoint } = useSelect( ( select ) => { +export default function InserterSidebar() { + const { insertionPoint, showMostUsedBlocks } = useSelect( ( select ) => { const { getInsertionPoint } = unlock( select( editorStore ) ); + const { get } = select( preferencesStore ); return { insertionPoint: getInsertionPoint(), + showMostUsedBlocks: get( 'core', 'mostUsedBlocks' ), }; }, [] ); const { setIsInserterOpened } = useDispatch( editorStore ); diff --git a/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js b/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js index b1940ef1b10813..84542937563acd 100644 --- a/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js +++ b/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js @@ -11,6 +11,7 @@ export default function convertEditorSettings( data ) { 'focusMode', 'inactivePanels', 'keepCaretInsideBlock', + 'mostUsedBlocks', 'openPanels', 'showBlockBreadcrumbs', 'showIconLabels', From 36fa07f70ea23f93e5b700d1f646cc24a30844e7 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Mon, 8 Jan 2024 12:26:30 +0100 Subject: [PATCH 04/51] DependencyExtractionWebpackPlugin: Add boolean shorthand for requestToExternalModule (#57593) The most common usage of the `requestToExternalModule` option is to return the same requested module ID that was passed in. A shorthand boolean return value is added support this behavior without returning the original requested module ID. --- packages/dependency-extraction-webpack-plugin/README.md | 7 +++++-- packages/dependency-extraction-webpack-plugin/lib/index.js | 4 ++++ .../dependency-extraction-webpack-plugin/lib/types.d.ts | 4 +++- .../test/fixtures/combine-assets/webpack.config.js | 4 +--- .../test/fixtures/dynamic-import/webpack.config.js | 4 +--- .../fixtures/function-output-filename/webpack.config.js | 4 +--- .../test/fixtures/has-extension-suffix/webpack.config.js | 4 +--- .../option-function-output-filename/webpack.config.js | 4 +--- .../test/fixtures/option-output-filename/webpack.config.js | 4 +--- .../test/fixtures/overrides/webpack.config.js | 4 +--- .../test/fixtures/runtime-chunk-single/webpack.config.js | 4 +--- .../test/fixtures/style-imports/webpack.config.js | 4 +--- .../test/fixtures/wordpress-require/webpack.config.js | 4 +--- .../test/fixtures/wordpress/webpack.config.js | 4 +--- 14 files changed, 23 insertions(+), 36 deletions(-) diff --git a/packages/dependency-extraction-webpack-plugin/README.md b/packages/dependency-extraction-webpack-plugin/README.md index 91fb36e8ad09d3..6031a1d5f25681 100644 --- a/packages/dependency-extraction-webpack-plugin/README.md +++ b/packages/dependency-extraction-webpack-plugin/README.md @@ -267,14 +267,17 @@ module.exports = { * * @param {string} request Requested module * - * @return {(string|undefined)} Script global + * @return {(string|boolean|undefined)} Module ID */ function requestToExternalModule( request ) { // Handle imports like `import myModule from 'my-module'` if ( request === 'my-module' ) { - // Import should be ov the form `import { something } from "myModule";` in the final bundle. + // Import should be of the form `import { something } from "myModule";` in the final bundle. return 'myModule'; } + + // If the Module ID in source is the same as the external module, we can return `true`. + return request === 'external-module-id-no-change-required'; } module.exports = { diff --git a/packages/dependency-extraction-webpack-plugin/lib/index.js b/packages/dependency-extraction-webpack-plugin/lib/index.js index 9b04d42e7c919d..e8d9fc9bfee1d8 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/index.js +++ b/packages/dependency-extraction-webpack-plugin/lib/index.js @@ -82,6 +82,10 @@ class DependencyExtractionWebpackPlugin { : defaultRequestToExternal( request ); } + if ( this.useModules && externalRequest === true ) { + externalRequest = request; + } + if ( externalRequest ) { this.externalizedDeps.add( request ); diff --git a/packages/dependency-extraction-webpack-plugin/lib/types.d.ts b/packages/dependency-extraction-webpack-plugin/lib/types.d.ts index c4a4af52b1b2fc..5a1bff90aa49ad 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/types.d.ts +++ b/packages/dependency-extraction-webpack-plugin/lib/types.d.ts @@ -13,7 +13,9 @@ declare interface DependencyExtractionWebpackPluginOptions { outputFormat?: 'php' | 'json'; outputFilename?: string | Function; requestToExternal?: ( request: string ) => string | string[] | undefined; - requestToExternalModule?: ( request: string ) => string | undefined; + requestToExternalModule?: ( + request: string + ) => string | boolean | undefined; requestToHandle?: ( request: string ) => string | undefined; combinedOutputFile?: string | null; combineAssets?: boolean; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js index fb7ba94ca80998..420d5030ab1a69 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js @@ -12,9 +12,7 @@ module.exports = { new DependencyExtractionWebpackPlugin( { combineAssets: true, requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/dynamic-import/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/dynamic-import/webpack.config.js index 6856d328ab7c68..59d4c5d2ead3ab 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/dynamic-import/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/dynamic-import/webpack.config.js @@ -7,9 +7,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js index f637a4087e3ca3..0aea733327a540 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js @@ -12,9 +12,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js index ada40c8bf8e54e..4ef05f6986b9c4 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js @@ -10,9 +10,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js index 5056f312c39992..40123021ae404c 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js @@ -10,9 +10,7 @@ module.exports = { return `chunk--${ chunkData.chunk.name }--[name].asset.php`; }, requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js index be52e661653868..fb02bc3c5bcd0b 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js @@ -8,9 +8,7 @@ module.exports = { new DependencyExtractionWebpackPlugin( { outputFilename: '[name]-foo.asset.php', requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/overrides/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/overrides/webpack.config.js index 89eaf6ee4b2f53..5e28a677f1d5b0 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/overrides/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/overrides/webpack.config.js @@ -23,9 +23,7 @@ module.exports = { if ( request === 'rxjs/operators' ) { return request; } - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, requestToHandle( request ) { if ( request === 'rxjs' || request === 'rxjs/operators' ) { diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js index 1e0824563c52f0..b8fa673995e9be 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js @@ -11,9 +11,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js index 332e182e34b042..bb412af5c61b89 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js @@ -12,9 +12,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), new MiniCSSExtractPlugin(), diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js index 6856d328ab7c68..59d4c5d2ead3ab 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js @@ -7,9 +7,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js index 6856d328ab7c68..59d4c5d2ead3ab 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js @@ -7,9 +7,7 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - if ( request.startsWith( '@wordpress/' ) ) { - return request; - } + return request.startsWith( '@wordpress/' ); }, } ), ], From ff97549ef1985524e2295b17e0be191db43130a2 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Mon, 8 Jan 2024 13:32:47 +0200 Subject: [PATCH 05/51] DataViews: Add duplicate template pattern action (#57638) --- .../create-template-part-modal/index.js | 165 +++++++++--------- .../dataviews-pattern-actions.js | 46 +++++ .../page-patterns/dataviews-patterns.js | 2 + .../src/components/page-patterns/style.scss | 12 +- 4 files changed, 140 insertions(+), 85 deletions(-) diff --git a/packages/edit-site/src/components/create-template-part-modal/index.js b/packages/edit-site/src/components/create-template-part-modal/index.js index 31f12b6cab56d3..b44446da0c0686 100644 --- a/packages/edit-site/src/components/create-template-part-modal/index.js +++ b/packages/edit-site/src/components/create-template-part-modal/index.js @@ -39,11 +39,25 @@ import { } from '../../utils/template-part-create'; export default function CreateTemplatePartModal( { + modalTitle = __( 'Create template part' ), + ...restProps +} ) { + return ( + + + + ); +} + +export function CreateTemplatePartModalContents( { defaultArea = TEMPLATE_PART_AREA_DEFAULT_CATEGORY, blocks = [], confirmLabel = __( 'Create' ), closeModal, - modalTitle = __( 'Create template part' ), onCreate, onError, defaultTitle = '', @@ -62,7 +76,6 @@ export default function CreateTemplatePartModal( { select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), [] ); - async function createTemplatePart() { if ( ! title || isSubmitting ) { return; @@ -105,91 +118,79 @@ export default function CreateTemplatePartModal( { setIsSubmitting( false ); } } - return ( - { + event.preventDefault(); + await createTemplatePart(); + } } > -
{ - event.preventDefault(); - await createTemplatePart(); - } } - > - - - + + + - - { templatePartAreas.map( - ( { - icon, - label, - area: value, - description, - } ) => ( - - - - - - - { label } -
{ description }
-
+ { templatePartAreas.map( + ( { icon, label, area: value, description } ) => ( + + + + + + + { label } +
{ description }
+
- - { area === value && ( - - ) } - -
-
- ) - ) } -
-
- - - - -
-
-
+ + { area === value && ( + + ) } + + + + ) + ) } + + + + + + + + ); } diff --git a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js index e49238495b5f3c..19d4c93bb685fe 100644 --- a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js +++ b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js @@ -35,6 +35,7 @@ import { TEMPLATE_PART_POST_TYPE, PATTERN_DEFAULT_CATEGORY, } from '../../utils/constants'; +import { CreateTemplatePartModalContents } from '../create-template-part-modal'; const { useHistory } = unlock( routerPrivateApis ); const { CreatePatternModalContents, useDuplicatePatternProps } = @@ -281,3 +282,48 @@ export const duplicatePatternAction = { ); }, }; + +export const duplicateTemplatePartAction = { + id: 'duplicate-template-part', + label: __( 'Duplicate' ), + isEligible: ( item ) => item.type === TEMPLATE_PART_POST_TYPE, + modalHeader: __( 'Duplicate template part' ), + RenderModal: ( { item, closeModal } ) => { + const { createSuccessNotice } = useDispatch( noticesStore ); + const { categoryId = PATTERN_DEFAULT_CATEGORY } = getQueryArgs( + window.location.href + ); + const history = useHistory(); + async function onTemplatePartSuccess( templatePart ) { + createSuccessNotice( + sprintf( + // translators: %s: The new template part's title e.g. 'Call to action (copy)'. + __( '"%s" duplicated.' ), + item.title + ), + { type: 'snackbar', id: 'edit-site-patterns-success' } + ); + history.push( { + postType: TEMPLATE_PART_POST_TYPE, + postId: templatePart?.id, + categoryType: TEMPLATE_PART_POST_TYPE, + categoryId, + } ); + closeModal(); + } + return ( + + ); + }, +}; diff --git a/packages/edit-site/src/components/page-patterns/dataviews-patterns.js b/packages/edit-site/src/components/page-patterns/dataviews-patterns.js index f7e86b8e2e23b1..bf18e0ffed1119 100644 --- a/packages/edit-site/src/components/page-patterns/dataviews-patterns.js +++ b/packages/edit-site/src/components/page-patterns/dataviews-patterns.js @@ -55,6 +55,7 @@ import { resetAction, deleteAction, duplicatePatternAction, + duplicateTemplatePartAction, } from './dataviews-pattern-actions'; import usePatternSettings from './use-pattern-settings'; import { unlock } from '../../lock-unlock'; @@ -318,6 +319,7 @@ export default function DataviewsPatterns() { () => [ renameAction, duplicatePatternAction, + duplicateTemplatePartAction, exportJSONaction, resetAction, deleteAction, diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index cce14e8067122d..dd3c52ef08c1a3 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -225,7 +225,7 @@ } /** - * DataViews patterns styles + * DataViews patterns styles. * TODO: when this becomes stable, consolidate styles with the above. */ .edit-site-page-patterns-dataviews { @@ -256,8 +256,6 @@ } } -// TODO: this is duplicated from `patterns-menu-items__convert-modal` styles, -// except for the `z-index`. Need to check if this is still needed. .dataviews-action-modal__duplicate-pattern { // Fix the modal width to prevent added categories from stretching the modal. [role="dialog"] > [role="document"] { @@ -283,3 +281,11 @@ max-height: $grid-unit-60 * 2; // Adjust to not cover the save button, showing three items. } } + +.dataviews-action-modal__duplicate-template-part { + .components-modal__frame { + @include break-small { + max-width: 500px; + } + } +} From 80f8d9e6a2770a390c87a0c9d1e6d62d54f7e2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 8 Jan 2024 14:09:07 +0100 Subject: [PATCH 06/51] Modules: Load the import map polyfill when needed (#57256) * Modules: Load the import map polyfill when needed * Update lib/experimental/modules/class-gutenberg-modules.php Co-authored-by: Felix Arntz --------- Co-authored-by: Felix Arntz --- .../modules/class-gutenberg-modules.php | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/experimental/modules/class-gutenberg-modules.php b/lib/experimental/modules/class-gutenberg-modules.php index 0a2da03545776e..5aa012f8e2f2c5 100644 --- a/lib/experimental/modules/class-gutenberg-modules.php +++ b/lib/experimental/modules/class-gutenberg-modules.php @@ -152,15 +152,21 @@ public static function print_module_preloads() { * import maps (https://github.com/guybedford/es-module-shims/issues/406). */ public static function print_import_map_polyfill() { - $import_map = self::get_import_map(); - if ( ! empty( $import_map['imports'] ) ) { - wp_print_script_tag( - array( - 'src' => gutenberg_url( '/build/modules/importmap-polyfill.min.js' ), - 'defer' => true, - ) - ); - } + $test = 'HTMLScriptElement.supports && HTMLScriptElement.supports("importmap")'; + $src = gutenberg_url( '/build/modules/importmap-polyfill.min.js' ); + + echo ( + // Test presence of feature... + '' + ); } /** @@ -273,4 +279,4 @@ function gutenberg_dequeue_module( $module_identifier ) { add_action( $modules_position, array( 'Gutenberg_Modules', 'print_module_preloads' ) ); // Prints the script that loads the import map polyfill in the footer. -add_action( 'wp_footer', array( 'Gutenberg_Modules', 'print_import_map_polyfill' ), 11 ); +add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_import_map_polyfill' ), 11 ); From 302c1487c0298a75b4a0094e77ec87256f15ccdf Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 8 Jan 2024 07:31:38 -0600 Subject: [PATCH 07/51] Font Library: unregister font collection (#54701) * remove font collection * add wp_unregister_font_collection function * format php * fix function comment * add tests for unregister_font_collection * removig unused variable * update wording on comments Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com> * keep track of unregistered font collection ids in an array * add tests to assure that get_font_collections returns an empty array if all the font collections were unregistered * fix tests * test the initial empty value of the font library collections * format * simplify unregistering of font collections and add _doing_it_wrong call * add translation domain * adding _doing_it_wrong if you are registering collections with the same id or unregistering a non existing collection id * updating tests * replace 6.4.0 by 6.5.0 in comments Co-authored-by: Vicente Canales <1157901+vcanales@users.noreply.github.com> * update version Co-authored-by: Grant Kinney * consolidate code as only one function to avoid code repetition * create base test case * format php * assertWPError * check that collection was not unregistered by mistake * calling parent test class mehtods * format php --------- Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com> Co-authored-by: Vicente Canales <1157901+vcanales@users.noreply.github.com> Co-authored-by: Grant Kinney --- .../font-library/class-wp-font-library.php | 50 +++++++++++++++-- .../fonts/font-library/font-library.php | 13 +++++ .../fonts/font-library/wpFontLibrary/base.php | 26 +++++++++ .../wpFontLibrary/getFontCollection.php | 8 +-- .../wpFontLibrary/getFontCollections.php | 26 +++------ .../wpFontLibrary/getFontsDir.php | 2 +- .../wpFontLibrary/getMimeTypes.php | 2 +- .../wpFontLibrary/registerFontCollection.php | 6 ++- .../wpFontLibrary/setUploadDir.php | 2 +- .../unregisterFontCollection.php | 54 +++++++++++++++++++ 10 files changed, 156 insertions(+), 33 deletions(-) create mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/base.php create mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php index 9320a554e510c7..59ec5e93fa787e 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -63,15 +63,57 @@ public static function get_expected_font_mime_types_per_php_version( $php_versio */ public static function register_font_collection( $config ) { $new_collection = new WP_Font_Collection( $config ); - - if ( isset( self::$collections[ $config['id'] ] ) ) { - return new WP_Error( 'font_collection_registration_error', 'Font collection already registered.' ); + if ( self::is_collection_registered( $config['id'] ) ) { + $error_message = sprintf( + /* translators: %s: Font collection id. */ + __( 'Font collection with id: "%s" is already registered.', 'default' ), + $config['id'] + ); + _doing_it_wrong( + __METHOD__, + $error_message, + '6.5.0' + ); + return new WP_Error( 'font_collection_registration_error', $error_message ); } - self::$collections[ $config['id'] ] = $new_collection; return $new_collection; } + /** + * Unregisters a previously registered font collection. + * + * @since 6.5.0 + * + * @param string $collection_id Font collection ID. + * @return bool True if the font collection was unregistered successfully and false otherwise. + */ + public static function unregister_font_collection( $collection_id ) { + if ( ! self::is_collection_registered( $collection_id ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Font collection id. */ + sprintf( __( 'Font collection "%s" not found.', 'default' ), $collection_id ), + '6.5.0' + ); + return false; + } + unset( self::$collections[ $collection_id ] ); + return true; + } + + /** + * Checks if a font collection is registered. + * + * @since 6.5.0 + * + * @param string $collection_id Font collection ID. + * @return bool True if the font collection is registered and false otherwise. + */ + private static function is_collection_registered( $collection_id ) { + return array_key_exists( $collection_id, self::$collections ); + } + /** * Gets all the font collections available. * diff --git a/lib/experimental/fonts/font-library/font-library.php b/lib/experimental/fonts/font-library/font-library.php index 709f63e9126cbc..711a6bb40c282b 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -60,6 +60,19 @@ function wp_register_font_collection( $config ) { } } +if ( ! function_exists( 'wp_unregister_font_collection' ) ) { + /** + * Unregisters a font collection from the Font Library. + * + * @since 6.5.0 + * + * @param string $collection_id The font collection ID. + */ + function wp_unregister_font_collection( $collection_id ) { + WP_Font_Library::unregister_font_collection( $collection_id ); + } + +} $default_font_collection = array( 'id' => 'default-font-collection', diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/base.php b/phpunit/tests/fonts/font-library/wpFontLibrary/base.php new file mode 100644 index 00000000000000..e8d970f5b3d393 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/base.php @@ -0,0 +1,26 @@ +getProperty( 'collections' ); + $property->setAccessible( true ); + $property->setValue( array() ); + } + + public function set_up() { + parent::set_up(); + $this->reset_font_collections(); + } + + public function tear_down() { + parent::tear_down(); + $this->reset_font_collections(); + } +} diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php index bfdb7258fa11aa..00d5ca2dcb2e73 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php @@ -10,20 +10,16 @@ * * @covers WP_Font_Library::get_font_collection */ -class Tests_Fonts_WpFontLibrary_GetFontCollection extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_GetFontCollection extends WP_Font_Library_UnitTestCase { - public static function set_up_before_class() { + public function test_should_get_font_collection() { $my_font_collection_config = array( 'id' => 'my-font-collection', 'name' => 'My Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => path_join( __DIR__, 'my-font-collection-data.json' ), ); - wp_register_font_collection( $my_font_collection_config ); - } - - public function test_should_get_font_collection() { $font_collection = WP_Font_Library::get_font_collection( 'my-font-collection' ); $this->assertInstanceOf( 'WP_Font_Collection', $font_collection ); } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php index 97e66e64e87161..40eacba8e18c56 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php @@ -10,11 +10,13 @@ * * @covers WP_Font_Library::get_font_collections */ -class Tests_Fonts_WpFontLibrary_GetFontCollections extends WP_UnitTestCase { - - public static function set_up_before_class() { - $font_library = new WP_Font_Library(); +class Tests_Fonts_WpFontLibrary_GetFontCollections extends WP_Font_Library_UnitTestCase { + public function test_should_get_an_empty_list() { + $font_collections = WP_Font_Library::get_font_collections(); + $this->assertEmpty( $font_collections, 'Should return an empty array.' ); + } + public function test_should_get_mock_font_collection() { $my_font_collection_config = array( 'id' => 'my-font-collection', 'name' => 'My Font Collection', @@ -22,23 +24,11 @@ public static function set_up_before_class() { 'src' => path_join( __DIR__, 'my-font-collection-data.json' ), ); - $font_library::register_font_collection( $my_font_collection_config ); - } - - public function test_should_get_the_default_font_collection() { - $font_collections = WP_Font_Library::get_font_collections(); - $this->assertArrayHasKey( 'default-font-collection', $font_collections, 'Default Google Fonts collection should be registered' ); - $this->assertInstanceOf( 'WP_Font_Collection', $font_collections['default-font-collection'], 'The value of the array $font_collections[id] should be an instance of WP_Font_Collection class.' ); - } + WP_Font_Library::register_font_collection( $my_font_collection_config ); - public function test_should_get_the_right_number_of_collections() { $font_collections = WP_Font_Library::get_font_collections(); $this->assertNotEmpty( $font_collections, 'Sould return an array of font collections.' ); - $this->assertCount( 2, $font_collections, 'Should return an array with one font collection.' ); - } - - public function test_should_get_mock_font_collection() { - $font_collections = WP_Font_Library::get_font_collections(); + $this->assertCount( 1, $font_collections, 'Should return an array with one font collection.' ); $this->assertArrayHasKey( 'my-font-collection', $font_collections, 'The array should have the key of the registered font collection id.' ); $this->assertInstanceOf( 'WP_Font_Collection', $font_collections['my-font-collection'], 'The value of the array $font_collections[id] should be an instance of WP_Font_Collection class.' ); } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php index 4bbafc55a2147c..1200200d7160b2 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Library::get_fonts_dir */ -class Tests_Fonts_WpFontLibrary_GetFontsDir extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_GetFontsDir extends WP_Font_Library_UnitTestCase { public function test_get_fonts_dir() { $this->assertStringEndsWith( '/wp-content/fonts', WP_Font_Library::get_fonts_dir() ); diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php index 708134af69e92a..485587060f16a1 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Family_Utils::get_expected_font_mime_types_per_php_version */ -class Tests_Fonts_WpFontsFamilyUtils_GetMimeTypes extends WP_UnitTestCase { +class Tests_Fonts_WpFontsFamilyUtils_GetMimeTypes extends WP_Font_Library_UnitTestCase { /** * diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php index 6bc5fbb8161cee..2569830f6bf2aa 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Library::register_font_collection */ -class Tests_Fonts_WpFontLibrary_RegisterFontCollection extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_RegisterFontCollection extends WP_Font_Library_UnitTestCase { public function test_should_register_font_collection() { $config = array( @@ -70,8 +70,10 @@ public function test_should_return_error_if_id_is_repeated() { $collection1 = WP_Font_Library::register_font_collection( $config1 ); $this->assertInstanceOf( 'WP_Font_Collection', $collection1, 'A collection should be registered.' ); + // Expects a _doing_it_wrong notice. + $this->setExpectedIncorrectUsage( 'WP_Font_Library::register_font_collection' ); // Try to register a second collection with same id. $collection2 = WP_Font_Library::register_font_collection( $config2 ); - $this->assertWPError( $collection2, 'Second collection with the same id should fail.' ); + $this->assertWPError( $collection2, 'A WP_Error should be returned.' ); } } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php index daa4c84aad9004..29d481d8afd6bc 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Library::set_upload_dir */ -class Tests_Fonts_WpFontLibrary_SetUploadDir extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_SetUploadDir extends WP_Font_Library_UnitTestCase { public function test_should_set_fonts_upload_dir() { $defaults = array( diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php new file mode 100644 index 00000000000000..e6e16956814fb4 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php @@ -0,0 +1,54 @@ + 'mock-font-collection-1', + 'name' => 'Mock Collection to be unregistered', + 'description' => 'A mock font collection to be unregistered.', + 'src' => 'my-collection-data.json', + ); + WP_Font_Library::register_font_collection( $config ); + + $config = array( + 'id' => 'mock-font-collection-2', + 'name' => 'Mock Collection', + 'description' => 'A mock font collection.', + 'src' => 'my-mock-data.json', + ); + WP_Font_Library::register_font_collection( $config ); + + // Unregister mock font collection. + WP_Font_Library::unregister_font_collection( 'mock-font-collection-1' ); + $collections = WP_Font_Library::get_font_collections(); + $this->assertArrayNotHasKey( 'mock-font-collection-1', $collections, 'Font collection was not unregistered.' ); + $this->assertArrayHasKey( 'mock-font-collection-2', $collections, 'Font collection was unregistered by mistake.' ); + + // Unregisters remaining mock font collection. + WP_Font_Library::unregister_font_collection( 'mock-font-collection-2' ); + $collections = WP_Font_Library::get_font_collections(); + $this->assertArrayNotHasKey( 'mock-font-collection-2', $collections, 'Mock font collection was not unregistered.' ); + + // Checks that all font collections were unregistered. + $this->assertEmpty( $collections, 'Font collections were not unregistered.' ); + } + + public function unregister_non_existing_collection() { + // Unregisters non existing font collection. + WP_Font_Library::unregister_font_collection( 'non-existing-collection' ); + $collections = WP_Font_Library::get_font_collections(); + $this->assertEmpty( $collections, 'Should not be registered collections.' ); + } +} From 6d4e6ddfccdaebbb75337564c331c402bb3b07d0 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 8 Jan 2024 08:12:23 -0600 Subject: [PATCH 08/51] Font Library: singularize install font families endpoint (#57569) * singularize install font families endpoint to accept only one font family instead of many in the same request * lint php * frontend changes to send only one font family per install request * restrict the upload of local font files to variants of only one font family * rename function to make it singular * fixing strings for translations * fix permission_callback value updated by mistake --- ...class-wp-rest-font-families-controller.php | 149 ++++----- .../font-library-modal/context.js | 14 +- .../font-library-modal/font-collection.js | 9 +- .../font-library-modal/local-fonts.js | 15 +- .../font-library-modal/resolvers.js | 2 +- .../font-library-modal/utils/index.js | 30 +- .../test/makeFormDataFromFontFamilies.spec.js | 62 ---- .../test/makeFormDataFromFontFamily.spec.js | 58 ++++ .../installFonts.php | 287 ++++++------------ 9 files changed, 258 insertions(+), 368 deletions(-) delete mode 100644 packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js create mode 100644 packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php index c92a0d2697f315..0147d80b7bde94 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php @@ -44,8 +44,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => function () { - return true;}, + 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), ), ) ); @@ -59,7 +58,7 @@ public function register_routes() { 'callback' => array( $this, 'install_fonts' ), 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), 'args' => array( - 'font_families' => array( + 'font_family_settings' => array( 'required' => true, 'type' => 'string', 'validate_callback' => array( $this, 'validate_install_font_families' ), @@ -92,85 +91,61 @@ public function register_routes() { * @param array $files Files to install. * @return array $error_messages Array of error messages. */ - private function get_validation_errors( $font_families, $files ) { + private function get_validation_errors( $font_family_settings, $files ) { $error_messages = array(); - if ( ! is_array( $font_families ) ) { - $error_messages[] = __( 'font_families should be an array of font families.', 'gutenberg' ); + if ( ! is_array( $font_family_settings ) ) { + $error_messages[] = __( 'font_family_settings should be a font family definition.', 'gutenberg' ); return $error_messages; } - // Checks if there is at least one font family. - if ( count( $font_families ) < 1 ) { - $error_messages[] = __( 'font_families should have at least one font family definition.', 'gutenberg' ); + if ( + ! isset( $font_family_settings['slug'] ) || + ! isset( $font_family_settings['name'] ) || + ! isset( $font_family_settings['fontFamily'] ) + ) { + $error_messages[] = __( 'Font family should have slug, name and fontFamily properties defined.', 'gutenberg' ); + return $error_messages; } - for ( $family_index = 0; $family_index < count( $font_families ); $family_index++ ) { - $font_family = $font_families[ $family_index ]; - - if ( - ! isset( $font_family['slug'] ) || - ! isset( $font_family['name'] ) || - ! isset( $font_family['fontFamily'] ) - ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have slug, name and fontFamily properties defined.', 'gutenberg' ), - $family_index - ); + if ( isset( $font_family_settings['fontFace'] ) ) { + if ( ! is_array( $font_family_settings['fontFace'] ) ) { + $error_messages[] = __( 'Font family should have fontFace property defined as an array.', 'gutenberg' ); } - if ( isset( $font_family['fontFace'] ) ) { - if ( ! is_array( $font_family['fontFace'] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have fontFace property defined as an array.', 'gutenberg' ), - $family_index - ); - continue; - } + if ( count( $font_family_settings['fontFace'] ) < 1 ) { + $error_messages[] = __( 'Font family should have at least one font face definition.', 'gutenberg' ); + } - if ( count( $font_family['fontFace'] ) < 1 ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have at least one font face definition.', 'gutenberg' ), - $family_index - ); - } + if ( ! empty( $font_family_settings['fontFace'] ) ) { + for ( $face_index = 0; $face_index < count( $font_family_settings['fontFace'] ); $face_index++ ) { - if ( ! empty( $font_family['fontFace'] ) ) { - for ( $face_index = 0; $face_index < count( $font_family['fontFace'] ); $face_index++ ) { + $font_face = $font_family_settings['fontFace'][ $face_index ]; + if ( ! isset( $font_face['fontWeight'] ) || ! isset( $font_face['fontStyle'] ) ) { + $error_messages[] = sprintf( + // translators: font face index. + __( 'Font family Font face [%1$s] should have fontWeight and fontStyle properties defined.', 'gutenberg' ), + $face_index + ); + } - $font_face = $font_family['fontFace'][ $face_index ]; - if ( ! isset( $font_face['fontWeight'] ) || ! isset( $font_face['fontStyle'] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] should have fontWeight and fontStyle properties defined.', 'gutenberg' ), - $family_index, - $face_index - ); - } + if ( isset( $font_face['downloadFromUrl'] ) && isset( $font_face['uploadedFile'] ) ) { + $error_messages[] = sprintf( + // translators: font face index. + __( 'Font family Font face [%1$s] should have only one of the downloadFromUrl or uploadedFile properties defined and not both.', 'gutenberg' ), + $face_index + ); + } - if ( isset( $font_face['downloadFromUrl'] ) && isset( $font_face['uploadedFile'] ) ) { + if ( isset( $font_face['uploadedFile'] ) ) { + if ( ! isset( $files[ $font_face['uploadedFile'] ] ) ) { $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] should have only one of the downloadFromUrl or uploadedFile properties defined and not both.', 'gutenberg' ), - $family_index, + // translators: font face index. + __( 'Font family Font face [%1$s] file is not defined in the request files.', 'gutenberg' ), $face_index ); } - - if ( isset( $font_face['uploadedFile'] ) ) { - if ( ! isset( $files[ $font_face['uploadedFile'] ] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] file is not defined in the request files.', 'gutenberg' ), - $family_index, - $face_index - ); - } - } } } } @@ -189,9 +164,9 @@ private function get_validation_errors( $font_families, $files ) { * @return true|WP_Error True if the parameter is valid, WP_Error otherwise. */ public function validate_install_font_families( $param, $request ) { - $font_families = json_decode( $param, true ); - $files = $request->get_file_params(); - $error_messages = $this->get_validation_errors( $font_families, $files ); + $font_family_settings = json_decode( $param, true ); + $files = $request->get_file_params(); + $error_messages = $this->get_validation_errors( $font_family_settings, $files ); if ( empty( $error_messages ) ) { return true; @@ -327,17 +302,15 @@ private function has_write_permission() { * * @since 6.5.0 * - * @param array[] $font_families Font families to install. + * @param array[] $font_family_settings Font family definition. * @return bool Whether the request needs write permissions. */ - private function needs_write_permission( $font_families ) { - foreach ( $font_families as $font ) { - if ( isset( $font['fontFace'] ) ) { - foreach ( $font['fontFace'] as $face ) { - // If the font is being downloaded from a URL or uploaded, it needs write permissions. - if ( isset( $face['downloadFromUrl'] ) || isset( $face['uploadedFile'] ) ) { - return true; - } + private function needs_write_permission( $font_family_settings ) { + if ( isset( $font_family_settings['fontFace'] ) ) { + foreach ( $font_family_settings['fontFace'] as $face ) { + // If the font is being downloaded from a URL or uploaded, it needs write permissions. + if ( isset( $face['downloadFromUrl'] ) || isset( $face['uploadedFile'] ) ) { + return true; } } } @@ -358,20 +331,20 @@ private function needs_write_permission( $font_families ) { */ public function install_fonts( $request ) { // Get new fonts to install. - $fonts_param = $request->get_param( 'font_families' ); + $font_family_settings = $request->get_param( 'font_family_settings' ); /* * As this is receiving form data, the font families are encoded as a string. * The form data is used because local fonts need to use that format to * attach the files in the request. */ - $fonts_to_install = json_decode( $fonts_param, true ); + $font_family_settings = json_decode( $font_family_settings, true ); $successes = array(); $errors = array(); $response_status = 200; - if ( empty( $fonts_to_install ) ) { + if ( empty( $font_family_settings ) ) { $errors[] = new WP_Error( 'no_fonts_to_install', __( 'No fonts to install', 'gutenberg' ) @@ -379,7 +352,7 @@ public function install_fonts( $request ) { $response_status = 400; } - if ( $this->needs_write_permission( $fonts_to_install ) ) { + if ( $this->needs_write_permission( $font_family_settings ) ) { $upload_dir = WP_Font_Library::get_fonts_dir(); if ( ! $this->has_upload_directory() ) { if ( ! wp_mkdir_p( $upload_dir ) ) { @@ -415,15 +388,13 @@ public function install_fonts( $request ) { } // Get uploaded files (used when installing local fonts). - $files = $request->get_file_params(); - foreach ( $fonts_to_install as $font_data ) { - $font = new WP_Font_Family( $font_data ); - $result = $font->install( $files ); - if ( is_wp_error( $result ) ) { - $errors[] = $result; - } else { - $successes[] = $result; - } + $files = $request->get_file_params(); + $font = new WP_Font_Family( $font_family_settings ); + $result = $font->install( $files ); + if ( is_wp_error( $result ) ) { + $errors[] = $result; + } else { + $successes[] = $result; } $data = array( diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/context.js b/packages/edit-site/src/components/global-styles/font-library-modal/context.js index 58b8621adcf0c3..e0749845788d60 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/context.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/context.js @@ -14,7 +14,7 @@ import { * Internal dependencies */ import { - fetchInstallFonts, + fetchInstallFont, fetchUninstallFonts, fetchFontCollections, fetchFontCollection, @@ -26,7 +26,7 @@ import { mergeFontFamilies, loadFontFaceInBrowser, getDisplaySrcFromFontFace, - makeFormDataFromFontFamilies, + makeFormDataFromFontFamily, } from './utils'; import { toggleFont } from './utils/toggleFont'; import getIntersectingFontFaces from './utils/get-intersecting-font-faces'; @@ -192,19 +192,19 @@ function FontLibraryProvider( { children } ) { return getActivatedFontsOutline( source )[ slug ] || []; }; - async function installFonts( fonts ) { + async function installFont( font ) { setIsInstalling( true ); try { // Prepare formData to install. - const formData = makeFormDataFromFontFamilies( fonts ); + const formData = makeFormDataFromFontFamily( font ); // Install the fonts (upload the font files to the server and create the post in the database). - const response = await fetchInstallFonts( formData ); + const response = await fetchInstallFont( formData ); const fontsInstalled = response?.successes || []; // Get intersecting font faces between the fonts we tried to installed and the fonts that were installed // (to avoid activating a non installed font). const fontToBeActivated = getIntersectingFontFaces( fontsInstalled, - fonts + [ font ] ); // Activate the font families (add the font families to the global styles). activateCustomFontFamilies( fontToBeActivated ); @@ -358,7 +358,7 @@ function FontLibraryProvider( { children } ) { isFontActivated, getFontFacesActivated, loadFontFaceAsset, - installFonts, + installFont, uninstallFont, toggleActivateFont, getAvailableFontsOutline, diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index 062ad232e3ca98..fc39e2e0096531 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -54,7 +54,7 @@ function FontCollection( { id } ) { const [ renderConfirmDialog, setRenderConfirmDialog ] = useState( requiresPermission && ! getGoogleFontsPermissionFromStorage() ); - const { collections, getFontCollection, installFonts } = + const { collections, getFontCollection, installFont } = useContext( FontLibraryContext ); const selectedCollection = collections.find( ( collection ) => collection.id === id @@ -92,6 +92,11 @@ function FontCollection( { id } ) { setNotice( null ); }, [ id ] ); + useEffect( () => { + // If the selected fonts change, reset the selected fonts to install + setFontsToInstall( [] ); + }, [ selectedFont ] ); + // Reset notice after 5 seconds useEffect( () => { if ( notice && notice?.duration !== 0 ) { @@ -149,7 +154,7 @@ function FontCollection( { id } ) { }; const handleInstall = async () => { - const response = await installFonts( fontsToInstall ); + const response = await installFont( fontsToInstall[ 0 ] ); const installNotice = getNoticeFromInstallResponse( response ); setNotice( installNotice ); resetFontsToInstall(); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js index 4030dcfb69a77f..d4221b420cb613 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js @@ -29,7 +29,7 @@ import { unlock } from '../../../lock-unlock'; const { ProgressBar } = unlock( componentsPrivateApis ); function LocalFonts() { - const { installFonts } = useContext( FontLibraryContext ); + const { installFont } = useContext( FontLibraryContext ); const [ notice, setNotice ] = useState( null ); const [ isUploading, setIsUploading ] = useState( false ); const supportedFormats = @@ -153,7 +153,18 @@ function LocalFonts() { */ const handleInstall = async ( fontFaces ) => { const fontFamilies = makeFamiliesFromFaces( fontFaces ); - const response = await installFonts( fontFamilies ); + + if ( fontFamilies.length > 1 ) { + setNotice( { + type: 'error', + message: __( + 'Variants from only one font family can be uploaded at a time.' + ), + } ); + return; + } + + const response = await installFont( fontFamilies[ 0 ] ); const installNotice = getNoticeFromInstallResponse( response ); setNotice( installNotice ); setIsUploading( false ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js index 0ab4a7ba742247..2e7f413a6fa45b 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js @@ -7,7 +7,7 @@ */ import apiFetch from '@wordpress/api-fetch'; -export async function fetchInstallFonts( data ) { +export async function fetchInstallFont( data ) { const config = { path: '/wp/v2/font-families', method: 'POST', diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js index 69db09d49a0cea..2874dd446efb45 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js @@ -130,16 +130,21 @@ export function getDisplaySrcFromFontFace( input, urlPrefix ) { return src; } -export function makeFormDataFromFontFamilies( fontFamilies ) { +export function makeFormDataFromFontFamily( fontFamily ) { const formData = new FormData(); - const newFontFamilies = fontFamilies.map( ( family, familyIndex ) => { - const { kebabCase } = unlock( componentsPrivateApis ); - family.slug = kebabCase( family.slug ); - if ( family?.fontFace ) { - family.fontFace = family.fontFace.map( ( face, faceIndex ) => { + const { kebabCase } = unlock( componentsPrivateApis ); + + const newFontFamily = { + ...fontFamily, + slug: kebabCase( fontFamily.slug ), + }; + + if ( newFontFamily?.fontFace ) { + const newFontFaces = newFontFamily.fontFace.map( + ( face, faceIndex ) => { if ( face.file ) { // Slugified file name because the it might contain spaces or characters treated differently on the server. - const fileId = `file-${ familyIndex }-${ faceIndex }`; + const fileId = `file-${ faceIndex }`; // Add the files to the formData formData.append( fileId, face.file, face.file.name ); // remove the file object from the face object the file is referenced by the uploadedFile key @@ -151,10 +156,11 @@ export function makeFormDataFromFontFamilies( fontFamilies ) { return newFace; } return face; - } ); - } - return family; - } ); - formData.append( 'font_families', JSON.stringify( newFontFamilies ) ); + } + ); + newFontFamily.fontFace = newFontFaces; + } + + formData.append( 'font_family_settings', JSON.stringify( newFontFamily ) ); return formData; } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js deleted file mode 100644 index 4adae7889cc5e5..00000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Internal dependencies - */ -import { makeFormDataFromFontFamilies } from '../index'; - -/* global File */ - -describe( 'makeFormDataFromFontFamilies', () => { - it( 'should process fontFamilies and return FormData', () => { - const mockFontFamilies = [ - { - slug: 'bebas', - name: 'Bebas', - fontFamily: 'Bebas', - fontFace: [ - { - file: new File( [ 'content' ], 'test-font1.woff2' ), - fontWeight: '500', - fontStyle: 'normal', - }, - { - file: new File( [ 'content' ], 'test-font2.woff2' ), - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const formData = makeFormDataFromFontFamilies( mockFontFamilies ); - - expect( formData instanceof FormData ).toBeTruthy(); - - // Check if files are added correctly - expect( formData.get( 'file-0-0' ).name ).toBe( 'test-font1.woff2' ); - expect( formData.get( 'file-0-1' ).name ).toBe( 'test-font2.woff2' ); - - // Check if 'fontFamilies' key in FormData is correct - const expectedFontFamilies = [ - { - fontFace: [ - { - fontWeight: '500', - fontStyle: 'normal', - uploadedFile: 'file-0-0', - }, - { - fontWeight: '400', - fontStyle: 'normal', - uploadedFile: 'file-0-1', - }, - ], - slug: 'bebas', - name: 'Bebas', - fontFamily: 'Bebas', - }, - ]; - expect( JSON.parse( formData.get( 'font_families' ) ) ).toEqual( - expectedFontFamilies - ); - } ); -} ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js new file mode 100644 index 00000000000000..9f38903c89759b --- /dev/null +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js @@ -0,0 +1,58 @@ +/** + * Internal dependencies + */ +import { makeFormDataFromFontFamily } from '../index'; + +/* global File */ + +describe( 'makeFormDataFromFontFamily', () => { + it( 'should process fontFamilies and return FormData', () => { + const mockFontFamily = { + slug: 'bebas', + name: 'Bebas', + fontFamily: 'Bebas', + fontFace: [ + { + file: new File( [ 'content' ], 'test-font1.woff2' ), + fontWeight: '500', + fontStyle: 'normal', + }, + { + file: new File( [ 'content' ], 'test-font2.woff2' ), + fontWeight: '400', + fontStyle: 'normal', + }, + ], + }; + + const formData = makeFormDataFromFontFamily( mockFontFamily ); + + expect( formData instanceof FormData ).toBeTruthy(); + + // Check if files are added correctly + expect( formData.get( 'file-0' ).name ).toBe( 'test-font1.woff2' ); + expect( formData.get( 'file-1' ).name ).toBe( 'test-font2.woff2' ); + + // Check if 'fontFamilies' key in FormData is correct + const expectedFontFamily = { + fontFace: [ + { + fontWeight: '500', + fontStyle: 'normal', + uploadedFile: 'file-0', + }, + { + fontWeight: '400', + fontStyle: 'normal', + uploadedFile: 'file-1', + }, + ], + slug: 'bebas', + name: 'Bebas', + fontFamily: 'Bebas', + }; + expect( JSON.parse( formData.get( 'font_family_settings' ) ) ).toEqual( + expectedFontFamily + ); + } ); +} ); diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php index d35022306f4e6f..98c1cb6e13fe5c 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php @@ -21,10 +21,10 @@ class Tests_Fonts_WPRESTFontFamiliesController_InstallFonts extends WP_REST_Font * @param array $files Font files to install. * @param array $expected_response Expected response data. */ - public function test_install_fonts( $font_families, $files, $expected_response ) { - $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); - $font_families_json = json_encode( $font_families ); - $install_request->set_param( 'font_families', $font_families_json ); + public function test_install_fonts( $font_family_settings, $files, $expected_response ) { + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $font_family_json = json_encode( $font_family_settings ); + $install_request->set_param( 'font_family_settings', $font_family_json ); $install_request->set_file_params( $files ); $response = rest_get_server()->dispatch( $install_request ); $data = $response->get_data(); @@ -68,38 +68,22 @@ public function data_install_fonts() { return array( 'google_fonts_to_download' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', ), ), ), - 'files' => array(), - 'expected_response' => array( + 'files' => array(), + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Piazzolla', @@ -114,55 +98,27 @@ public function data_install_fonts() { ), ), ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => '/wp-content/fonts/montserrat_normal_100.ttf', - ), - ), - ), ), 'errors' => array(), ), ), 'google_fonts_to_use_as_is' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', ), ), ), - 'files' => array(), - 'expected_response' => array( + 'files' => array(), + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Piazzolla', @@ -177,35 +133,19 @@ public function data_install_fonts() { ), ), ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - - ), - ), - ), ), 'errors' => array(), ), ), 'fonts_without_font_faces' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Arial', - 'slug' => 'arial', - 'name' => 'Arial', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Arial', + 'slug' => 'arial', + 'name' => 'Arial', ), - 'files' => array(), - 'expected_response' => array( + 'files' => array(), + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Arial', @@ -218,35 +158,20 @@ public function data_install_fonts() { ), 'fonts_with_local_fonts_assets' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files0', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'uploadedFile' => 'files1', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files0', ), ), ), - 'files' => array( + 'files' => array( 'files0' => array( 'name' => 'piazzola1.ttf', 'type' => 'font/ttf', @@ -262,7 +187,7 @@ public function data_install_fonts() { 'size' => 123, ), ), - 'expected_response' => array( + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Piazzolla', @@ -277,20 +202,6 @@ public function data_install_fonts() { ), ), ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => '/wp-content/fonts/montserrat_normal_100.ttf', - ), - ), - ), - ), 'errors' => array(), ), @@ -325,15 +236,15 @@ public function data_install_with_improper_inputs() { return array( 'not a font families array' => array( - 'font_families' => 'This is not an array', + 'font_family_settings' => 'This is not an array', ), 'empty array' => array( - 'font_families' => array(), + 'font_family_settings' => array(), ), 'without slug' => array( - 'font_families' => array( + 'font_family_settings' => array( array( 'fontFamily' => 'Piazzolla', 'name' => 'Piazzolla', @@ -342,63 +253,55 @@ public function data_install_with_improper_inputs() { ), 'with improper font face property' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => 'This is not an array', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => 'This is not an array', ), ), 'with empty font face property' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array(), - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array(), ), ), 'fontface referencing uploaded file without uploaded files' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files0', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files0', ), ), ), - 'files' => array(), + 'files' => array(), ), 'fontface referencing uploaded file without uploaded files' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files666', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files666', ), ), ), - 'files' => array( + 'files' => array( 'files0' => array( 'name' => 'piazzola1.ttf', 'type' => 'font/ttf', @@ -410,20 +313,18 @@ public function data_install_with_improper_inputs() { ), 'fontface with incompatible properties (downloadFromUrl and uploadedFile together)' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'uploadedFile' => 'files0', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'uploadedFile' => 'files0', ), ), ), From 73474eba696b897627a0f7b3dd84a646e294e690 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:21:44 +0900 Subject: [PATCH 09/51] Add Template Modal: Update scroll related layout (#57617) --- packages/edit-site/src/components/add-new-template/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/add-new-template/style.scss b/packages/edit-site/src/components/add-new-template/style.scss index b1c2b669e24cef..4881745c92afae 100644 --- a/packages/edit-site/src/components/add-new-template/style.scss +++ b/packages/edit-site/src/components/add-new-template/style.scss @@ -39,7 +39,8 @@ .edit-site-custom-template-modal__suggestions_list { @include break-small() { - overflow: scroll; + max-height: $grid-unit-70 * 4; // Height of four buttons + overflow-y: auto; } &__list-item { From 1b5ebca728074569e1ddff9928badcc882627e1c Mon Sep 17 00:00:00 2001 From: James Koster Date: Mon, 8 Jan 2024 15:53:35 +0000 Subject: [PATCH 10/51] Data views: Make title display in grid views consistent (#57553) --- packages/dataviews/src/style.scss | 26 ++++++++++++------- .../edit-site/src/components/list/style.scss | 5 ---- .../src/components/page-pages/index.js | 2 +- .../src/components/page-pages/style.scss | 6 ----- .../page-patterns/dataviews-patterns.js | 5 +++- .../src/components/page-templates/index.js | 2 +- 6 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index edf1500d2cc5ad..5a2bccdef68f2d 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -149,10 +149,22 @@ } .dataviews-view-grid__card { - h3 { // Todo: A better way to target this - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + .dataviews-view-grid__primary-field { + .dataviews-view-grid__title-field { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + font-size: $default-font-size; + width: 100%; + } + + .dataviews-view-grid__title-field a, + button.dataviews-view-grid__title-field { + font-weight: 500; + color: $gray-900; + text-decoration: none; + } } } @@ -173,12 +185,6 @@ .dataviews-view-grid__primary-field { min-height: $grid-unit-30; - - a { - color: $gray-900; - text-decoration: none; - font-weight: 500; - } } .dataviews-view-grid__fields { diff --git a/packages/edit-site/src/components/list/style.scss b/packages/edit-site/src/components/list/style.scss index 9d6e0f0f1d6e24..cfc65252c8e685 100644 --- a/packages/edit-site/src/components/list/style.scss +++ b/packages/edit-site/src/components/list/style.scss @@ -186,8 +186,3 @@ display: block; color: $gray-700; } - -.edit-site-list-title__customized-info { - font-size: $default-font-size; - font-weight: 500; -} diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 164b4daf8603c0..8729922faabea1 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -222,7 +222,7 @@ export default function PagePages() { { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type diff --git a/packages/edit-site/src/components/page-pages/style.scss b/packages/edit-site/src/components/page-pages/style.scss index 35ac8273dc555a..933fdadb8d070e 100644 --- a/packages/edit-site/src/components/page-pages/style.scss +++ b/packages/edit-site/src/components/page-pages/style.scss @@ -3,9 +3,3 @@ width: $grid-unit-40; height: $grid-unit-40; } - - -.edit-site-page-pages__list-view-title-field { - font-size: $default-font-size; - font-weight: 500; -} diff --git a/packages/edit-site/src/components/page-patterns/dataviews-patterns.js b/packages/edit-site/src/components/page-patterns/dataviews-patterns.js index bf18e0ffed1119..ad474d882cfcf6 100644 --- a/packages/edit-site/src/components/page-patterns/dataviews-patterns.js +++ b/packages/edit-site/src/components/page-patterns/dataviews-patterns.js @@ -199,7 +199,9 @@ function Title( { item, categoryId } ) { ) } { item.type === PATTERN_TYPES.theme ? ( - item.title + + { item.title } + ) : ( diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index 1aaf1e153d0c50..c0e0289311db6a 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -103,7 +103,7 @@ function TemplateTitle( { item, viewType } ) { return ( - + Date: Mon, 8 Jan 2024 16:01:18 +0000 Subject: [PATCH 11/51] Update Table layout design details (#57644) --- packages/dataviews/src/pagination.js | 4 +-- packages/dataviews/src/style.scss | 30 +++++++++++++++---- packages/dataviews/src/view-table.js | 8 +++-- .../src/components/page-pages/style.scss | 5 ++-- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/dataviews/src/pagination.js b/packages/dataviews/src/pagination.js index 21aeda8a602a1a..2c9cade42d89b7 100644 --- a/packages/dataviews/src/pagination.js +++ b/packages/dataviews/src/pagination.js @@ -23,8 +23,8 @@ const Pagination = memo( function Pagination( { totalPages !== 1 && ( diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index 5a2bccdef68f2d..049f006dd97bd5 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -88,6 +88,11 @@ td:first-child, th:first-child { padding-left: $grid-unit-40; + + .dataviews-table-header-button, + .dataviews-table-header { + margin-left: - #{$grid-unit-10}; + } } td:last-child, @@ -112,18 +117,27 @@ th { position: sticky; top: -1px; - background-color: lighten($gray-100, 4%); + background-color: $white; box-shadow: inset 0 -#{$border-width} 0 $gray-100; - border-top: 1px solid $gray-100; - padding-top: $grid-unit-05; - padding-bottom: $grid-unit-05; + padding-top: $grid-unit-10; + padding-bottom: $grid-unit-10; z-index: 1; + font-size: 11px; + text-transform: uppercase; + font-weight: 500; + padding-left: $grid-unit-05; } } .dataviews-table-header-button { - padding: 0; - gap: $grid-unit-05; + padding: $grid-unit-05 $grid-unit-10; + font-size: 11px; + text-transform: uppercase; + font-weight: 500; + + &:not(:hover) { + color: $gray-900; + } span { speak: none; @@ -133,6 +147,10 @@ } } } + + .dataviews-table-header { + padding-left: $grid-unit-05; + } } .dataviews-grid-view { diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js index 083931bb5203ec..0be2ee6767ddff 100644 --- a/packages/dataviews/src/view-table.js +++ b/packages/dataviews/src/view-table.js @@ -91,8 +91,8 @@ const HeaderMenu = forwardRef( function HeaderMenu( + { showAddPageModal && ( + + ) } + + } > Date: Wed, 10 Jan 2024 17:57:07 +0900 Subject: [PATCH 39/51] Image Block: Change upload icon label (#57704) --- packages/block-library/src/image/image.js | 2 +- test/e2e/specs/editor/blocks/image.spec.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index ea12457c2585db..d8788fde4844f6 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -459,7 +459,7 @@ export default function Image( { diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js index adeabc860c8342..9080a6dc194021 100644 --- a/test/e2e/specs/editor/blocks/image.spec.js +++ b/test/e2e/specs/editor/blocks/image.spec.js @@ -696,7 +696,9 @@ test.describe( 'Image', () => { await expect( linkDom ).toHaveAttribute( 'href', url ); } ); - test( 'should upload external image', async ( { editor } ) => { + test( 'should upload external image to media library', async ( { + editor, + } ) => { await editor.insertBlock( { name: 'core/image', attributes: { @@ -704,7 +706,7 @@ test.describe( 'Image', () => { }, } ); - await editor.clickBlockToolbarButton( 'Upload external image' ); + await editor.clickBlockToolbarButton( 'Upload image to media library' ); const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' From 3eb3eca08a90c36603ade6a754c1ab2fc4e6d92a Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 10 Jan 2024 10:18:54 +0100 Subject: [PATCH 40/51] Use full text instead of abbreviation for min height setting. (#57680) --- .../src/components/global-styles/dimensions-panel.js | 4 ++-- packages/block-editor/src/components/height-control/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/dimensions-panel.js b/packages/block-editor/src/components/global-styles/dimensions-panel.js index 47b5bd329725a7..47e50aa515e3c6 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -603,7 +603,7 @@ export default function DimensionsPanel( { { showMinHeightControl && ( diff --git a/packages/block-editor/src/components/height-control/README.md b/packages/block-editor/src/components/height-control/README.md index 67b52f1d56f9b2..9be1741e8cdd8e 100644 --- a/packages/block-editor/src/components/height-control/README.md +++ b/packages/block-editor/src/components/height-control/README.md @@ -43,7 +43,7 @@ A callback function that handles the application of the height value. - **Type:** `String` - **Default:** `'Height'` -A label for the height control. This is useful when using the height control for a feature that is controlled in the same way as height, but requires a different label. For example, "Min. height". +A label for the height control. This is useful when using the height control for a feature that is controlled in the same way as height, but requires a different label. For example, "Minimum height". ## Related components From 4c9b8cc09fc57f5d62b740b5eef3c6672f0b116a Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 10 Jan 2024 13:22:33 +0400 Subject: [PATCH 41/51] Editor: Use hooks instead of HoCs in 'PostVisibilityCheck' (#57705) --- .../src/components/post-visibility/check.js | 25 +++++-------- .../components/post-visibility/test/check.js | 37 ++++++++++++------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/packages/editor/src/components/post-visibility/check.js b/packages/editor/src/components/post-visibility/check.js index 4bf9bd03772da6..116db0f546de2b 100644 --- a/packages/editor/src/components/post-visibility/check.js +++ b/packages/editor/src/components/post-visibility/check.js @@ -1,26 +1,21 @@ /** * WordPress dependencies */ -import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -export function PostVisibilityCheck( { hasPublishAction, render } ) { - const canEdit = hasPublishAction; +export default function PostVisibilityCheck( { render } ) { + const canEdit = useSelect( ( select ) => { + return ( + select( editorStore ).getCurrentPost()._links?.[ + 'wp:action-publish' + ] ?? false + ); + } ); + return render( { canEdit } ); } - -export default compose( [ - withSelect( ( select ) => { - const { getCurrentPost, getCurrentPostType } = select( editorStore ); - return { - hasPublishAction: - getCurrentPost()._links?.[ 'wp:action-publish' ] ?? false, - postType: getCurrentPostType(), - }; - } ), -] )( PostVisibilityCheck ); diff --git a/packages/editor/src/components/post-visibility/test/check.js b/packages/editor/src/components/post-visibility/test/check.js index 8ec0c2df04ec90..828e876cceb102 100644 --- a/packages/editor/src/components/post-visibility/test/check.js +++ b/packages/editor/src/components/post-visibility/test/check.js @@ -3,32 +3,43 @@ */ import { render, screen } from '@testing-library/react'; +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() ); + /** * Internal dependencies */ -import { PostVisibilityCheck } from '../check'; +import PostVisibilityCheck from '../check'; + +function setupMockSelect( hasPublishAction ) { + useSelect.mockImplementation( ( mapSelect ) => { + return mapSelect( () => ( { + getCurrentPost: () => ( { + _links: { + 'wp:action-publish': hasPublishAction, + }, + } ), + } ) ); + } ); +} describe( 'PostVisibilityCheck', () => { const renderProp = ( { canEdit } ) => ( canEdit ? 'yes' : 'no' ); it( "should not render the edit link if the user doesn't have the right capability", () => { - render( - - ); + setupMockSelect( false ); + render( ); expect( screen.queryByText( 'yes' ) ).not.toBeInTheDocument(); expect( screen.getByText( 'no' ) ).toBeVisible(); } ); it( 'should render if the user has the correct capability', () => { - render( - - ); + setupMockSelect( true ); + render( ); expect( screen.queryByText( 'no' ) ).not.toBeInTheDocument(); expect( screen.getByText( 'yes' ) ).toBeVisible(); } ); From 6ef4629f77ab3da6502508b4c9539a3912300f0e Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 10 Jan 2024 10:35:55 +0100 Subject: [PATCH 42/51] Clean up code editor CSS. (#57519) --- packages/edit-post/src/components/text-editor/style.scss | 6 ------ packages/edit-site/src/components/code-editor/style.scss | 4 ---- 2 files changed, 10 deletions(-) diff --git a/packages/edit-post/src/components/text-editor/style.scss b/packages/edit-post/src/components/text-editor/style.scss index c02e983057e6ef..ab248317de1dbf 100644 --- a/packages/edit-post/src/components/text-editor/style.scss +++ b/packages/edit-post/src/components/text-editor/style.scss @@ -39,10 +39,8 @@ margin-right: auto; @include break-large() { - padding: $grid-unit-20 $grid-unit-30 #{ $grid-unit-60 * 2 } $grid-unit-30; padding: 0 $grid-unit-30 $grid-unit-30 $grid-unit-30; } - } // Exit code editor toolbar. @@ -70,8 +68,4 @@ font-size: $default-font-size; color: $gray-900; } - - .components-button svg { - order: 1; - } } diff --git a/packages/edit-site/src/components/code-editor/style.scss b/packages/edit-site/src/components/code-editor/style.scss index 0e79575c49f678..17431de27b896c 100644 --- a/packages/edit-site/src/components/code-editor/style.scss +++ b/packages/edit-site/src/components/code-editor/style.scss @@ -41,10 +41,6 @@ font-size: $default-font-size; color: $gray-900; } - - .components-button svg { - order: 1; - } } } From 73a4716f429b5dce0190638049f5bd30f0b242f6 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Jan 2024 12:47:03 +0100 Subject: [PATCH 43/51] Scripts: Fix webpack not setting environment.module true (#57714) Webpack (via wp-scripts) may refuse to output external modules unless we set `output.environment.modules = true`: > The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module' > while analyzing module external module "@wordpress/interactivity" for concatenation --- packages/scripts/config/webpack.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 3919558c2f05ca..57bd258d325393 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -403,6 +403,10 @@ if ( hasExperimentalModulesFlag ) { ...baseConfig.output, module: true, chunkFormat: 'module', + environment: { + ...baseConfig.output.environment, + module: true, + }, library: { ...baseConfig.output.library, type: 'module', From b3e534e636cc286ca79a796d3a47164ff362302b Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 10 Jan 2024 13:35:36 +0100 Subject: [PATCH 44/51] Add tests to cover enabling autoplay and loop setting in Audio block (#57715) --- .../test/__snapshots__/edit.native.js.snap | 12 ++++++++ .../src/audio/test/edit.native.js | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap index dca3f782efc676..4cf28f7063ad31 100644 --- a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap @@ -532,3 +532,15 @@ exports[`Audio block renders placeholder without crashing 1`] = ` `; + +exports[`Audio block should enable autoplay setting 1`] = ` +" +
+" +`; + +exports[`Audio block should enable loop setting 1`] = ` +" +
+" +`; diff --git a/packages/block-library/src/audio/test/edit.native.js b/packages/block-library/src/audio/test/edit.native.js index c191fd2fff7989..7296d595d7aaab 100644 --- a/packages/block-library/src/audio/test/edit.native.js +++ b/packages/block-library/src/audio/test/edit.native.js @@ -5,7 +5,10 @@ import { addBlock, dismissModal, fireEvent, + getBlock, + getEditorHtml, initializeEditor, + openBlockSettings, render, screen, setupCoreBlocks, @@ -31,6 +34,10 @@ jest.unmock( '@wordpress/react-native-aztec' ); const MEDIA_UPLOAD_STATE_FAILED = 3; +const AUDIO_BLOCK = ` +
+`; + let uploadCallBack; subscribeMediaUpload.mockImplementation( ( callback ) => { uploadCallBack = callback; @@ -100,4 +107,26 @@ describe( 'Audio block', () => { screen.getByText( 'Invalid URL. Audio file not found.' ) ).toBeVisible(); } ); + + it( 'should enable autoplay setting', async () => { + await initializeEditor( { initialHtml: AUDIO_BLOCK } ); + + const audioBlock = getBlock( screen, 'Audio' ); + fireEvent.press( audioBlock ); + await openBlockSettings( screen ); + + fireEvent.press( screen.getByText( 'Autoplay' ) ); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'should enable loop setting', async () => { + await initializeEditor( { initialHtml: AUDIO_BLOCK } ); + + const audioBlock = getBlock( screen, 'Audio' ); + fireEvent.press( audioBlock ); + await openBlockSettings( screen ); + + fireEvent.press( screen.getByText( 'Loop' ) ); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); } ); From db067e16df101128fe7588e483112c7030dfa83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:51:21 +0100 Subject: [PATCH 45/51] DataViews: add footer to Pages sidebar (#57690) --- .../index.js | 77 +++++++++++++++++++ .../edit-site/src/components/sidebar/index.js | 9 +-- 2 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js new file mode 100644 index 00000000000000..171d59c108e9b8 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { + __experimentalTruncate as Truncate, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { layout } from '@wordpress/icons'; +import { useMemo } from '@wordpress/element'; +import { useEntityRecords } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { useLink } from '../routes/link'; +import { TEMPLATE_POST_TYPE } from '../../utils/constants'; +import SidebarNavigationItem from '../sidebar-navigation-item'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import DataViewsSidebarContent from '../sidebar-dataviews'; + +const PageItem = ( { postType = 'page', postId, ...props } ) => { + const linkInfo = useLink( + { + postType, + postId, + }, + { + backPath: '/page', + } + ); + return ; +}; + +export default function SidebarNavigationScreenPagesDataViews() { + const { records: templateRecords } = useEntityRecords( + 'postType', + TEMPLATE_POST_TYPE, + { + per_page: -1, + } + ); + const templates = useMemo( + () => + templateRecords?.filter( ( { slug } ) => + [ '404', 'search' ].includes( slug ) + ), + [ templateRecords ] + ); + + return ( + } + footer={ + + { templates?.map( ( item ) => ( + + + { decodeEntities( + item.title?.rendered || __( '(no title)' ) + ) } + + + ) ) } + + } + /> + ); +} diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 0f986d486fbb81..73c6aea7e328c5 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -7,7 +7,6 @@ import classNames from 'classnames'; * WordPress dependencies */ import { memo, useRef } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; import { __experimentalNavigatorProvider as NavigatorProvider, __experimentalNavigatorScreen as NavigatorScreen, @@ -32,9 +31,8 @@ import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen import SaveHub from '../save-hub'; import { unlock } from '../../lock-unlock'; import SidebarNavigationScreenPages from '../sidebar-navigation-screen-pages'; +import SidebarNavigationScreenPagesDataViews from '../sidebar-navigation-screen-pages-dataviews'; import SidebarNavigationScreenPage from '../sidebar-navigation-screen-page'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import DataViewsSidebarContent from '../sidebar-dataviews'; const { useLocation } = unlock( routerPrivateApis ); @@ -69,10 +67,7 @@ function SidebarScreens() { { window?.__experimentalAdminViews ? ( - } - /> + ) : ( ) } From 08f236d0319f503c1c784dc8050df058cbccdea9 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Jan 2024 15:13:22 +0100 Subject: [PATCH 46/51] Interactive template: Use viewModule (#57712) Use viewModule in the create-block interactivity template. - Add `viewModule` support to `@wordpress/create-block`. - Set the `--experimental-modules` option when building the templated block. - The plugin php template is updated to register with block metadata. --------- Co-authored-by: Luis Herranz --- .../CHANGELOG.md | 14 +++++++++----- .../create-block-interactive-template/README.md | 2 ++ .../block-templates/render.php.mustache | 5 ----- .../create-block-interactive-template/index.js | 6 ++++++ .../plugin-templates/$slug.php.mustache | 11 +---------- packages/create-block/CHANGELOG.md | 4 ++++ packages/create-block/lib/init-block.js | 2 ++ packages/create-block/lib/scaffold.js | 2 ++ packages/interactivity/docs/1-getting-started.md | 16 ++-------------- 9 files changed, 28 insertions(+), 34 deletions(-) diff --git a/packages/create-block-interactive-template/CHANGELOG.md b/packages/create-block-interactive-template/CHANGELOG.md index 47a8aec6c92a31..159c65e9ada19c 100644 --- a/packages/create-block-interactive-template/CHANGELOG.md +++ b/packages/create-block-interactive-template/CHANGELOG.md @@ -2,20 +2,24 @@ ## Unreleased +### Enhancement + +- Update the template to use `viewModule` in block.json ([#57712](https://github.com/WordPress/gutenberg/pull/57712)). + ## 1.11.0 (2023-12-13) -- Add all files to the generated plugin zip. [#56943](https://github.com/WordPress/gutenberg/pull/56943) -- Prevent crash when Gutenberg plugin is not installed. [#56941](https://github.com/WordPress/gutenberg/pull/56941) +- Add all files to the generated plugin zip ([#56943](https://github.com/WordPress/gutenberg/pull/56943)). +- Prevent crash when Gutenberg plugin is not installed ([#56941](https://github.com/WordPress/gutenberg/pull/56941)). ## 1.10.1 (2023-12-07) -- Update template to use modules instead of scripts. [#56694](https://github.com/WordPress/gutenberg/pull/56694) +- Update template to use modules instead of scripts ([#56694](https://github.com/WordPress/gutenberg/pull/56694)). ## 1.10.0 (2023-11-29) ### Enhancement -- Update `view.js` and `render.php` templates to the new `store()` API. [#56613](https://github.com/WordPress/gutenberg/pull/56613) +- Update `view.js` and `render.php` templates to the new `store()` API ([#56613](https://github.com/WordPress/gutenberg/pull/56613)). ## 1.9.0 (2023-11-16) @@ -35,4 +39,4 @@ ### Enhancement -- Moves the `example` property into block.json by leveraging changes to create-block to now support `example`. [#52801](https://github.com/WordPress/gutenberg/pull/52801) +- Moves the `example` property into block.json by leveraging changes to create-block to now support `example` ([#52801](https://github.com/WordPress/gutenberg/pull/52801)). diff --git a/packages/create-block-interactive-template/README.md b/packages/create-block-interactive-template/README.md index cc0530c0630549..adf3cab6594cc9 100644 --- a/packages/create-block-interactive-template/README.md +++ b/packages/create-block-interactive-template/README.md @@ -10,6 +10,8 @@ This block template can be used by running the following command: npx @wordpress/create-block --template @wordpress/create-block-interactive-template ``` +It requires Gutenberg 17.5 or higher. + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. diff --git a/packages/create-block-interactive-template/block-templates/render.php.mustache b/packages/create-block-interactive-template/block-templates/render.php.mustache index 0f6883a9362407..960da619f790a4 100644 --- a/packages/create-block-interactive-template/block-templates/render.php.mustache +++ b/packages/create-block-interactive-template/block-templates/render.php.mustache @@ -13,11 +13,6 @@ // Generate unique id for aria-controls. $unique_id = wp_unique_id( 'p-' ); - -// Enqueue the view file. -if (function_exists('gutenberg_enqueue_module')) { - gutenberg_enqueue_module( '{{namespace}}-view' ); -} ?>
!! value ) diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index 49d3cbf794777a..bd9ba0396b75e3 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -44,6 +44,7 @@ module.exports = async ( editorStyle, style, render, + viewModule, viewScript, variantVars, customPackageJSON, @@ -84,6 +85,7 @@ module.exports = async ( editorStyle, style, render, + viewModule, viewScript, variantVars, customPackageJSON, diff --git a/packages/interactivity/docs/1-getting-started.md b/packages/interactivity/docs/1-getting-started.md index 85af2021807351..660671a8b10cd8 100644 --- a/packages/interactivity/docs/1-getting-started.md +++ b/packages/interactivity/docs/1-getting-started.md @@ -26,18 +26,6 @@ We can scaffold a WordPress plugin that registers an interactive block (using th npx @wordpress/create-block@latest my-first-interactive-block --template @wordpress/create-block-interactive-template ``` -> **Note** -> The Interactivity API recently switched from [using modules instead of scripts in the frontend](https://github.com/WordPress/gutenberg/pull/56143). Therefore, in order to test this scaffolded block, you will need to add the following line to the `package.json` file of the generated plugin: - -```json -"files": [ - "src/view.js" -] -``` -> This should be updated in the [scripts package](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) soon. - - - #### 2. Generate the build When the plugin folder is generated, we should launch the build process to get the final version of the interactive block that can be used from WordPress. @@ -61,7 +49,7 @@ At this point you should be able to insert the "My First Interactive Block" bloc ## Requirements of the Interactivity API -To start working with the Interactivity API you'll need to have a [proper WordPress development environment for blocks](https://developer.wordpress.org/block-editor/getting-started/devenv/) and some specific code in your block, which should include: +To start working with the Interactivity API you'll need to have a [proper WordPress development environment for blocks](https://developer.wordpress.org/block-editor/getting-started/devenv/) and some specific code in your block, which should include: #### A local WordPress installation @@ -71,7 +59,7 @@ To get quickly started, [`wp-now`](https://www.npmjs.com/package/@wp-now/wp-now) #### Latest vesion of Gutenberg -The Interactivity API is currently only available as an experimental feature from Gutenberg 17.2, so you'll need to have Gutenberg 17.2 or higher version installed and activated in your WordPress installation. +The Interactivity API is currently only available as an experimental feature from Gutenberg, so you'll need to have Gutenberg 17.5 or higher version installed and activated in your WordPress installation. #### Node.js From ba367dcc86de157f74eebf419f167381b5d797b2 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 10 Jan 2024 15:28:52 +0100 Subject: [PATCH 47/51] Update @ariakit/react to v0.3.12 and @ariakit/test to v0.3.7 (#57547) * Update @ariakit/react to v0.3.12 and @ariakit/test to v0.3.7 * CHANGELOG * Use @ariakit/test for AlignmentMatrixControl unit tests * Use @ariakit/text for ToggleGroupControl unit tests * Refactor 'hoverOutside' method * Improve tooltip-related tests in `ToggleGroupControl` --- package-lock.json | 152 ++++++++---------- package.json | 2 +- packages/components/CHANGELOG.md | 1 + packages/components/package.json | 2 +- .../alignment-matrix-control/test/index.tsx | 26 ++- .../src/toggle-group-control/test/index.tsx | 103 ++++++------ 6 files changed, 140 insertions(+), 146 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2c4a65342a353..02de0a60ff4c93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -86,7 +86,7 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "5.0.0", - "@ariakit/test": "^0.3.5", + "@ariakit/test": "^0.3.7", "@babel/core": "7.16.0", "@babel/plugin-proposal-export-namespace-from": "7.18.9", "@babel/plugin-syntax-jsx": "7.16.0", @@ -1628,13 +1628,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ariakit/core": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.10.tgz", + "integrity": "sha512-AcN+GSoVXuUOzKx5d3xPL3YsEHevh4PIO6QIt/mg/nRX1XQ6cvxQEiAjO/BJQm+/MVl7/VbuGBoTFjr0tPU6NQ==" + }, + "node_modules/@ariakit/react": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.12.tgz", + "integrity": "sha512-HxKMZZhWSkwwS/Sh9OdWyuNKQ2tjDAIQIy2KVI7IRa8ZQ6ze/4g3YLUHbfCxO7oDupXHfXaeZ4hWx8lP7l1U/g==", + "dependencies": { + "@ariakit/react-core": "0.3.12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ariakit" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@ariakit/react-core": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.12.tgz", + "integrity": "sha512-w6P1A7TYb1fKUe9QbwaoTOWofl13g7TEuXdV4JyefJCQL1e9HQdEw9UL67I8aXRo8/cFHH94/z0N37t8hw5Ogg==", + "dependencies": { + "@ariakit/core": "0.3.10", + "@floating-ui/dom": "^1.0.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@ariakit/test": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.5.tgz", - "integrity": "sha512-7UCQBnJZ88JptkEnAXT7iSgtxEZiFwqdkKtxLCXDssTOJNatbFsnq0Jow324y41jGfAE2n4Lf5qY2FsZUPf9XQ==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.7.tgz", + "integrity": "sha512-rOa9pJA0ZfPPSI4SkDX41CsBcvxs6BmxgzFEElZWZo/uBBqtnr8ZL4oe5HySeZKEAHRH86XDqfxFISkhV76m5g==", "dev": true, "dependencies": { - "@ariakit/core": "0.3.8", + "@ariakit/core": "0.3.10", "@testing-library/dom": "^8.0.0 || ^9.0.0" }, "peerDependencies": { @@ -1650,12 +1685,6 @@ } } }, - "node_modules/@ariakit/test/node_modules/@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==", - "dev": true - }, "node_modules/@aw-web-design/x-default-browser": { "version": "1.4.126", "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz", @@ -54244,7 +54273,7 @@ "version": "25.14.0", "license": "GPL-2.0-or-later", "dependencies": { - "@ariakit/react": "^0.3.10", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -54303,41 +54332,6 @@ "react-dom": "^18.0.0" } }, - "packages/components/node_modules/@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==" - }, - "packages/components/node_modules/@ariakit/react": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.10.tgz", - "integrity": "sha512-XRY69IOm8Oy+HSPoaspcVLAhLo3ToLhhJKSLK1voTAZtSzu5kUeUf4nUPxTzYFsvirKORZgOLAeNwuo1gPr61g==", - "dependencies": { - "@ariakit/react-core": "0.3.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ariakit" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, - "packages/components/node_modules/@ariakit/react-core": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.10.tgz", - "integrity": "sha512-CzSffcNlOyS2xuy21UB6fgJXi5LriJ9JrTSJzcgJmE+P9/WfQlplJC3L75d8O2yKgaGPeFnQ0hhDA6ItsI98eQ==", - "dependencies": { - "@ariakit/core": "0.3.8", - "@floating-ui/dom": "^1.0.0", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, "packages/components/node_modules/@floating-ui/react-dom": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", @@ -57392,22 +57386,37 @@ } } }, + "@ariakit/core": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.10.tgz", + "integrity": "sha512-AcN+GSoVXuUOzKx5d3xPL3YsEHevh4PIO6QIt/mg/nRX1XQ6cvxQEiAjO/BJQm+/MVl7/VbuGBoTFjr0tPU6NQ==" + }, + "@ariakit/react": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.12.tgz", + "integrity": "sha512-HxKMZZhWSkwwS/Sh9OdWyuNKQ2tjDAIQIy2KVI7IRa8ZQ6ze/4g3YLUHbfCxO7oDupXHfXaeZ4hWx8lP7l1U/g==", + "requires": { + "@ariakit/react-core": "0.3.12" + } + }, + "@ariakit/react-core": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.12.tgz", + "integrity": "sha512-w6P1A7TYb1fKUe9QbwaoTOWofl13g7TEuXdV4JyefJCQL1e9HQdEw9UL67I8aXRo8/cFHH94/z0N37t8hw5Ogg==", + "requires": { + "@ariakit/core": "0.3.10", + "@floating-ui/dom": "^1.0.0", + "use-sync-external-store": "^1.2.0" + } + }, "@ariakit/test": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.5.tgz", - "integrity": "sha512-7UCQBnJZ88JptkEnAXT7iSgtxEZiFwqdkKtxLCXDssTOJNatbFsnq0Jow324y41jGfAE2n4Lf5qY2FsZUPf9XQ==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.7.tgz", + "integrity": "sha512-rOa9pJA0ZfPPSI4SkDX41CsBcvxs6BmxgzFEElZWZo/uBBqtnr8ZL4oe5HySeZKEAHRH86XDqfxFISkhV76m5g==", "dev": true, "requires": { - "@ariakit/core": "0.3.8", + "@ariakit/core": "0.3.10", "@testing-library/dom": "^8.0.0 || ^9.0.0" - }, - "dependencies": { - "@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==", - "dev": true - } } }, "@aw-web-design/x-default-browser": { @@ -69363,7 +69372,7 @@ "@wordpress/components": { "version": "file:packages/components", "requires": { - "@ariakit/react": "^0.3.10", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -69415,29 +69424,6 @@ "valtio": "1.7.0" }, "dependencies": { - "@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==" - }, - "@ariakit/react": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.10.tgz", - "integrity": "sha512-XRY69IOm8Oy+HSPoaspcVLAhLo3ToLhhJKSLK1voTAZtSzu5kUeUf4nUPxTzYFsvirKORZgOLAeNwuo1gPr61g==", - "requires": { - "@ariakit/react-core": "0.3.10" - } - }, - "@ariakit/react-core": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.10.tgz", - "integrity": "sha512-CzSffcNlOyS2xuy21UB6fgJXi5LriJ9JrTSJzcgJmE+P9/WfQlplJC3L75d8O2yKgaGPeFnQ0hhDA6ItsI98eQ==", - "requires": { - "@ariakit/core": "0.3.8", - "@floating-ui/dom": "^1.0.0", - "use-sync-external-store": "^1.2.0" - } - }, "@floating-ui/react-dom": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", diff --git a/package.json b/package.json index e98ad7cb1587f5..f2f239762de976 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "5.0.0", - "@ariakit/test": "^0.3.5", + "@ariakit/test": "^0.3.7", "@babel/core": "7.16.0", "@babel/plugin-proposal-export-namespace-from": "7.18.9", "@babel/plugin-syntax-jsx": "7.16.0", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 8805736c2e4409..c7c1a515a64cec 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -24,6 +24,7 @@ ### Enhancements - Update `ariakit` to version `0.3.10` ([#57325](https://github.com/WordPress/gutenberg/pull/57325)). +- Update `@ariakit/react` to version `0.3.12` and @ariakit/test to version `0.3.7` ([#57547](https://github.com/WordPress/gutenberg/pull/57547)). - `DropdownMenuV2`: do not collapse suffix width ([#57238](https://github.com/WordPress/gutenberg/pull/57238)). - `DateTimePicker`: Adjustment of the dot position on DayButton and expansion of the button area. ([#55502](https://github.com/WordPress/gutenberg/pull/55502)). - `Modal`: Improve application of body class names ([#55430](https://github.com/WordPress/gutenberg/pull/55430)). diff --git a/packages/components/package.json b/packages/components/package.json index 885c1e455fea40..cd440998b93230 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -30,7 +30,7 @@ ], "types": "build-types", "dependencies": { - "@ariakit/react": "^0.3.10", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", diff --git a/packages/components/src/alignment-matrix-control/test/index.tsx b/packages/components/src/alignment-matrix-control/test/index.tsx index 6836bc7e45f95c..a820b69b26c8ff 100644 --- a/packages/components/src/alignment-matrix-control/test/index.tsx +++ b/packages/components/src/alignment-matrix-control/test/index.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { render, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { press, click } from '@ariakit/test'; /** * Internal dependencies @@ -37,11 +37,9 @@ describe( 'AlignmentMatrixControl', () => { } ); it( 'should be centered by default', async () => { - const user = userEvent.setup(); - await renderAndInitCompositeStore( ); - await user.tab(); + await press.Tab(); expect( getCell( 'center center' ) ).toHaveFocus(); } ); @@ -60,7 +58,6 @@ describe( 'AlignmentMatrixControl', () => { 'bottom center', 'bottom right', ] )( '%s', async ( alignment ) => { - const user = userEvent.setup(); const spy = jest.fn(); await renderAndInitCompositeStore( @@ -72,14 +69,13 @@ describe( 'AlignmentMatrixControl', () => { const cell = getCell( alignment ); - await user.click( cell ); + await click( cell ); expect( cell ).toHaveFocus(); expect( spy ).toHaveBeenCalledWith( alignment ); } ); it( 'unless already focused', async () => { - const user = userEvent.setup(); const spy = jest.fn(); await renderAndInitCompositeStore( @@ -91,7 +87,7 @@ describe( 'AlignmentMatrixControl', () => { const cell = getCell( 'center center' ); - await user.click( cell ); + await click( cell ); expect( cell ).toHaveFocus(); expect( spy ).not.toHaveBeenCalled(); @@ -106,16 +102,15 @@ describe( 'AlignmentMatrixControl', () => { [ 'ArrowLeft', 'center left' ], [ 'ArrowDown', 'bottom center' ], [ 'ArrowRight', 'center right' ], - ] )( '%s', async ( keyRef, cellRef ) => { - const user = userEvent.setup(); + ] as const )( '%s', async ( keyRef, cellRef ) => { const spy = jest.fn(); await renderAndInitCompositeStore( ); - await user.tab(); - await user.keyboard( `[${ keyRef }]` ); + await press.Tab(); + await press[ keyRef ](); expect( getCell( cellRef ) ).toHaveFocus(); expect( spy ).toHaveBeenCalledWith( cellRef ); @@ -128,8 +123,7 @@ describe( 'AlignmentMatrixControl', () => { [ 'ArrowLeft', 'top left' ], [ 'ArrowDown', 'bottom right' ], [ 'ArrowRight', 'bottom right' ], - ] )( '%s', async ( keyRef, cellRef ) => { - const user = userEvent.setup(); + ] as const )( '%s', async ( keyRef, cellRef ) => { const spy = jest.fn(); await renderAndInitCompositeStore( @@ -137,8 +131,8 @@ describe( 'AlignmentMatrixControl', () => { ); const cell = getCell( cellRef ); - await user.click( cell ); - await user.keyboard( `[${ keyRef }]` ); + await click( cell ); + await press[ keyRef ](); expect( cell ).toHaveFocus(); expect( spy ).toHaveBeenCalledWith( cellRef ); diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx index b54b5764d4e0ff..99a2dd8a00421c 100644 --- a/packages/components/src/toggle-group-control/test/index.tsx +++ b/packages/components/src/toggle-group-control/test/index.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { press, click, hover, sleep } from '@ariakit/test'; /** * WordPress dependencies @@ -19,8 +19,13 @@ import { ToggleGroupControlOption, ToggleGroupControlOptionIcon, } from '../index'; +import { TOOLTIP_DELAY } from '../../tooltip'; import type { ToggleGroupControlProps } from '../types'; -import cleanupTooltip from '../../tooltip/test/utils'; + +const hoverOutside = async () => { + await hover( document.body ); + await hover( document.body, { clientX: 10, clientY: 10 } ); +}; const ControlledToggleGroupControl = ( { value: valueProp, @@ -113,7 +118,6 @@ describe.each( [ } ); } ); it( 'should call onChange with proper value', async () => { - const user = userEvent.setup(); const mockOnChange = jest.fn(); render( @@ -126,13 +130,12 @@ describe.each( [ ); - await user.click( screen.getByRole( 'radio', { name: 'R' } ) ); + await click( screen.getByRole( 'radio', { name: 'R' } ) ); expect( mockOnChange ).toHaveBeenCalledWith( 'rigas' ); } ); it( 'should render tooltip where `showTooltip` === `true`', async () => { - const user = userEvent.setup(); render( { optionsWithTooltip } @@ -143,19 +146,26 @@ describe.each( [ 'Click for Delicious Gnocchi' ); - await user.hover( firstRadio ); + await hover( firstRadio ); - const tooltip = await screen.findByText( - 'Click for Delicious Gnocchi' - ); + const tooltip = await screen.findByRole( 'tooltip', { + name: 'Click for Delicious Gnocchi', + } ); await waitFor( () => expect( tooltip ).toBeVisible() ); - await cleanupTooltip( user ); + // hover outside of radio + await hoverOutside(); + + // Tooltip should hide + expect( + screen.queryByRole( 'tooltip', { + name: 'Click for Delicious Gnocchi', + } ) + ).not.toBeInTheDocument(); } ); it( 'should not render tooltip', async () => { - const user = userEvent.setup(); render( { optionsWithTooltip } @@ -166,19 +176,24 @@ describe.each( [ 'Click for Sumptuous Caponata' ); - await user.hover( secondRadio ); + await hover( secondRadio ); - await waitFor( () => - expect( - screen.queryByText( 'Click for Sumptuous Caponata' ) - ).not.toBeInTheDocument() - ); + // Tooltip shouldn't show + expect( + screen.queryByText( 'Click for Sumptuous Caponata' ) + ).not.toBeInTheDocument(); + + // Advance time by default delay + await sleep( TOOLTIP_DELAY ); + + // Tooltip shouldn't show. + expect( + screen.queryByText( 'Click for Sumptuous Caponata' ) + ).not.toBeInTheDocument(); } ); if ( mode === 'controlled' ) { it( 'should reset values correctly', async () => { - const user = userEvent.setup(); - render( { options } @@ -188,25 +203,23 @@ describe.each( [ const rigasOption = screen.getByRole( 'radio', { name: 'R' } ); const jackOption = screen.getByRole( 'radio', { name: 'J' } ); - await user.click( rigasOption ); + await click( rigasOption ); expect( jackOption ).not.toBeChecked(); expect( rigasOption ).toBeChecked(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( rigasOption ).not.toBeChecked(); expect( jackOption ).toBeChecked(); - await user.click( screen.getByRole( 'button', { name: 'Reset' } ) ); + await click( screen.getByRole( 'button', { name: 'Reset' } ) ); expect( rigasOption ).not.toBeChecked(); expect( jackOption ).not.toBeChecked(); } ); it( 'should update correctly when triggered by external updates', async () => { - const user = userEvent.setup(); - render( { it( 'should not be deselectable', async () => { const mockOnChange = jest.fn(); - const user = userEvent.setup(); render( { - const user = userEvent.setup(); - render( - - { options } - + <> + + { options } + + + ); const rigas = screen.getByRole( 'radio', { name: 'R', } ); - await user.tab(); + await press.Tab(); expect( rigas ).toHaveFocus(); - await user.tab(); + await press.Tab(); + // When in controlled mode, there is an additional "Reset" button. const expectedFocusTarget = mode === 'uncontrolled' - ? rigas.ownerDocument.body + ? screen.getByRole( 'button', { + name: 'After ToggleGroupControl', + } ) : screen.getByRole( 'button', { name: 'Reset' } ); expect( expectedFocusTarget ).toHaveFocus(); @@ -301,7 +317,6 @@ describe.each( [ describe( 'isDeselectable = true', () => { it( 'should be deselectable', async () => { const mockOnChange = jest.fn(); - const user = userEvent.setup(); render( ); - await user.click( + await click( screen.getByRole( 'button', { name: 'R', pressed: true, @@ -323,7 +338,7 @@ describe.each( [ expect( mockOnChange ).toHaveBeenCalledTimes( 1 ); expect( mockOnChange ).toHaveBeenLastCalledWith( undefined ); - await user.click( + await click( screen.getByRole( 'button', { name: 'R', pressed: false, @@ -334,15 +349,13 @@ describe.each( [ } ); it( 'should tab to the next option button', async () => { - const user = userEvent.setup(); - render( { options } ); - await user.tab(); + await press.Tab(); expect( screen.getByRole( 'button', { name: 'R', @@ -350,7 +363,7 @@ describe.each( [ } ) ).toHaveFocus(); - await user.tab(); + await press.Tab(); expect( screen.getByRole( 'button', { name: 'J', @@ -359,7 +372,7 @@ describe.each( [ ).toHaveFocus(); // Focus should not move with arrow keys - await user.keyboard( '{ArrowLeft}' ); + await press.ArrowLeft(); expect( screen.getByRole( 'button', { name: 'J', From 6693377d080d8c581f279bfd7521f5b2bcf02149 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 10 Jan 2024 14:34:24 +0000 Subject: [PATCH 48/51] Bump plugin version to 17.5.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 35e416006bea50..9559f838608da9 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.3 * Requires PHP: 7.0 - * Version: 17.4.1 + * Version: 17.5.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 02de0a60ff4c93..96a14ec8eeb50e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.4.1", + "version": "17.5.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.4.1", + "version": "17.5.0-rc.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index f2f239762de976..684f35d408d3c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.4.1", + "version": "17.5.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From e93f250746b14acaed14190cdb552960a6c490ce Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Wed, 10 Jan 2024 15:49:08 +0100 Subject: [PATCH 49/51] [Mobile] - Fix missing custom gradient indicator in the color palette (#57605) * Mobile - Fix missing Custom indicator for custom gradients * Pass enableCustomColor in Cover block as true * Fix typo * Update Changelog * Fix condition * Update test to use the openBlockSettings helper * Simplify condition by adding optional chaining * Use flatMap --- .../test/__snapshots__/edit.native.js.snap | 6 +++ .../src/buttons/test/edit.native.js | 49 +++++++++++++++++++ .../block-library/src/cover/edit.native.js | 1 + .../src/color-palette/index.native.js | 25 +++++++--- .../color-settings/palette.screen.native.js | 12 +++-- packages/react-native-editor/CHANGELOG.md | 1 + 6 files changed, 82 insertions(+), 12 deletions(-) diff --git a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap index 1a55c807225d9d..f04eacee4b91c1 100644 --- a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap @@ -6,6 +6,12 @@ exports[`Buttons block color customization sets a background color 1`] = ` " `; +exports[`Buttons block color customization sets a custom gradient background color 1`] = ` +" +
+" +`; + exports[`Buttons block color customization sets a gradient background color 1`] = ` "
diff --git a/packages/block-library/src/buttons/test/edit.native.js b/packages/block-library/src/buttons/test/edit.native.js index f393a31c7330ad..af2ffe762e6a36 100644 --- a/packages/block-library/src/buttons/test/edit.native.js +++ b/packages/block-library/src/buttons/test/edit.native.js @@ -10,6 +10,7 @@ import { initializeEditor, triggerBlockListLayout, typeInRichText, + openBlockSettings, waitFor, } from 'test/helpers'; @@ -391,5 +392,53 @@ describe( 'Buttons block', () => { // Assert expect( getEditorHtml() ).toMatchSnapshot(); } ); + + it( 'sets a custom gradient background color', async () => { + // Arrange + const screen = await initializeEditor(); + await addBlock( screen, 'Buttons' ); + + // Act + const buttonsBlock = getBlock( screen, 'Buttons' ); + fireEvent.press( buttonsBlock ); + + // Trigger onLayout for the list + await triggerBlockListLayout( buttonsBlock ); + + const buttonBlock = await getBlock( screen, 'Button' ); + fireEvent.press( buttonBlock ); + + // Open Block Settings. + await openBlockSettings( screen ); + + // Open Text color settings + fireEvent.press( screen.getByLabelText( 'Background, Default' ) ); + + // Tap on the gradient segment + fireEvent.press( screen.getByLabelText( 'Gradient' ) ); + + // Tap one gradient color + fireEvent.press( + screen.getByLabelText( 'Light green cyan to vivid green cyan' ) + ); + + // Tap on Customize Gradient + fireEvent.press( screen.getByLabelText( /Customize Gradient/ ) ); + + // Change the current angle + fireEvent.press( screen.getByText( '135', { hidden: true } ) ); + const angleTextInput = screen.getByDisplayValue( '135', { + hidden: true, + } ); + fireEvent.changeText( angleTextInput, '200' ); + + // Go back to the settings list. + fireEvent.press( await screen.findByLabelText( 'Go back' ) ); + + // Assert + const customButton = await screen.findByText( 'CUSTOM' ); + expect( customButton ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); } ); } ); diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index 81ff43128b1a35..989c5ec3a0d332 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -538,6 +538,7 @@ const Cover = ( { { ( { shouldEnableBottomSheetScroll } ) => ( color ) ), ]; - const mergedColors = [ + const mergedGradients = [ + ...new Set( + ( defaultSettings.gradients ?? [] ).map( + ( { gradient } ) => gradient + ) + ), + ]; + const allAvailableColors = [ ...new Set( ( defaultSettings.allColors ?? [] ).map( ( { color } ) => color ) ), ]; - const defaultGradientColors = [ + const allAvailableGradients = [ ...new Set( - ( defaultSettings.gradients ?? [] ).map( + ( defaultSettings.allGradients ?? [] ).map( ( { gradient } ) => gradient ) ), ]; - const colors = isGradientSegment ? defaultGradientColors : defaultColors; + + const colors = isGradientSegment ? mergedGradients : mergedColors; + const allColors = isGradientSegment + ? allAvailableGradients + : allAvailableColors; const customIndicatorColor = isGradientSegment ? activeColor @@ -110,7 +121,7 @@ function ColorPalette( { function isSelectedCustom() { const isWithinColors = - activeColor && mergedColors && mergedColors.includes( activeColor ); + activeColor && allColors?.includes( activeColor ); if ( enableCustomColor && activeColor ) { if ( isGradientSegment ) { return isGradientColor && ! isWithinColors; diff --git a/packages/components/src/mobile/color-settings/palette.screen.native.js b/packages/components/src/mobile/color-settings/palette.screen.native.js index bc7187fd092b8c..fcf03f9ecd4483 100644 --- a/packages/components/src/mobile/color-settings/palette.screen.native.js +++ b/packages/components/src/mobile/color-settings/palette.screen.native.js @@ -29,7 +29,6 @@ import { colorsUtils } from './utils'; import styles from './style.scss'; const HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 }; -const THEME_PALETTE_NAME = 'Theme'; const PaletteScreen = () => { const route = useRoute(); @@ -48,7 +47,6 @@ const PaletteScreen = () => { const [ currentValue, setCurrentValue ] = useState( colorValue ); const isGradientColor = isGradient( currentValue ); const selectedSegmentIndex = isGradientColor ? 1 : 0; - const allAvailableColors = useMobileGlobalStylesColors(); const [ currentSegment, setCurrentSegment ] = useState( segments[ selectedSegmentIndex ] @@ -57,6 +55,10 @@ const PaletteScreen = () => { const currentSegmentColors = ! isGradientSegment ? defaultSettings.colors : defaultSettings.gradients; + const allAvailableColors = useMobileGlobalStylesColors(); + const allAvailableGradients = currentSegmentColors + .flatMap( ( { gradients } ) => gradients ) + .filter( Boolean ); const horizontalSeparatorStyle = usePreferredColorSchemeStyle( styles.horizontalSeparator, @@ -184,10 +186,10 @@ const PaletteScreen = () => { colors: palette.colors, gradients: palette.gradients, allColors: allAvailableColors, + allGradients: allAvailableGradients, }; - const enableCustomColor = - ! isGradientSegment && - palette.name === THEME_PALETTE_NAME; + // Limit to show the custom indicator to the first available palette + const enableCustomColor = paletteKey === 0; return ( Date: Wed, 10 Jan 2024 09:58:32 -0500 Subject: [PATCH 50/51] Font Library: filter fonts upload directory (#57697) * add global configuration variables for font directory * add multi-site based directory path for fonts * add docblock for get_multi_site_font_sub_dir * Rename function for accuracy. * Use filter instead of constant to determine where fonts are uploaded. * Format php. * simplify the filters code and output the same array as upload_filter does * remove tests no longer used * add a test case for the filter * rename function. Replaces the misleading 'subdir' term by 'dir'. --------- Co-authored-by: madhusudhand Co-authored-by: Matias Benedetto --- .../font-library/class-wp-font-family.php | 4 +- .../font-library/class-wp-font-library.php | 76 +++++++++++++------ .../font-library/wpFontLibrary/fontsDir.php | 70 +++++++++++++++++ .../wpFontLibrary/getFontsDir.php | 18 ----- .../wpFontLibrary/setUploadDir.php | 32 -------- 5 files changed, 126 insertions(+), 74 deletions(-) create mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php delete mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php delete mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php index a4204dfe1fa2c7..e47cf0afdac1de 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family.php @@ -599,9 +599,9 @@ private function create_or_update_font_post() { */ public function install( $files = null ) { add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); - add_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + add_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) ); $were_assets_written = $this->download_or_move_font_faces( $files ); - remove_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + remove_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) ); remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); if ( ! $were_assets_written ) { diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php index 59ec5e93fa787e..99de81e0bd74a3 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -141,40 +141,72 @@ public static function get_font_collection( $id ) { } /** - * Gets the upload directory for fonts. + * Returns an array containing the current fonts upload directory's path and URL. * * @since 6.5.0 * - * @return string Path of the upload directory for fonts. + * @param array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } + * + * @return array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } */ - public static function get_fonts_dir() { - return path_join( WP_CONTENT_DIR, 'fonts' ); + public static function fonts_dir( $defaults = array() ) { + $site_path = self::get_multi_site_dir(); + + // Sets the defaults. + $defaults['path'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['url'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['subdir'] = ''; + $defaults['basedir'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['baseurl'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['error'] = false; + + // Filters the fonts directory data. + return apply_filters( 'fonts_dir', $defaults ); } /** - * Sets the upload directory for fonts. + * Gets the Site dir for fonts, using the blog ID if multi-site, empty otherwise. * * @since 6.5.0 * - * @param array $defaults { - * Default upload directory. + * @return string Site dir path. + */ + private static function get_multi_site_dir() { + $font_sub_dir = ''; + if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { + $font_sub_dir = '/sites/' . get_current_blog_id(); + } + return $font_sub_dir; + } + + /** + * Gets the upload directory for fonts. * - * @type string $path Path to the directory. - * @type string $url URL for the directory. - * @type string $subdir Sub-directory of the directory. - * @type string $basedir Base directory. - * @type string $baseurl Base URL. - * } - * @return array Modified upload directory. + * @since 6.5.0 + * + * @return string Path of the upload directory for fonts. */ - public static function set_upload_dir( $defaults ) { - $defaults['basedir'] = WP_CONTENT_DIR; - $defaults['baseurl'] = content_url(); - $defaults['subdir'] = '/fonts'; - $defaults['path'] = self::get_fonts_dir(); - $defaults['url'] = $defaults['baseurl'] . '/fonts'; - - return $defaults; + public static function get_fonts_dir() { + $fonts_dir_settings = self::fonts_dir(); + return $fonts_dir_settings['path']; } /** diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php new file mode 100644 index 00000000000000..9926bb74090888 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php @@ -0,0 +1,70 @@ +dir_defaults = array( + 'path' => path_join( WP_CONTENT_DIR, 'fonts' ), + 'url' => content_url( 'fonts' ), + 'subdir' => '', + 'basedir' => path_join( WP_CONTENT_DIR, 'fonts' ), + 'baseurl' => content_url( 'fonts' ), + 'error' => false, + ); + } + + public function test_fonts_dir() { + $fonts_dir = WP_Font_Library::fonts_dir(); + $this->assertEquals( $fonts_dir, $this->dir_defaults ); + } + + public function test_fonts_dir_with_filter() { + // Define a callback function to pass to the filter. + function set_new_values( $defaults ) { + $defaults['path'] = '/custom-path/fonts/my-custom-subdir'; + $defaults['url'] = 'http://example.com/custom-path/fonts/my-custom-subdir'; + $defaults['subdir'] = 'my-custom-subdir'; + $defaults['basedir'] = '/custom-path/fonts'; + $defaults['baseurl'] = 'http://example.com/custom-path/fonts'; + $defaults['error'] = false; + return $defaults; + } + + // Add the filter. + add_filter( 'fonts_dir', 'set_new_values' ); + + // Gets the fonts dir. + $fonts_dir = WP_Font_Library::fonts_dir(); + + $expected = array( + 'path' => '/custom-path/fonts/my-custom-subdir', + 'url' => 'http://example.com/custom-path/fonts/my-custom-subdir', + 'subdir' => 'my-custom-subdir', + 'basedir' => '/custom-path/fonts', + 'baseurl' => 'http://example.com/custom-path/fonts', + 'error' => false, + ); + + $this->assertEquals( $fonts_dir, $expected, 'The fonts_dir() method should return the expected values.' ); + + // Remove the filter. + remove_filter( 'fonts_dir', 'set_new_values' ); + + // Gets the fonts dir. + $fonts_dir = WP_Font_Library::fonts_dir(); + + $this->assertEquals( $fonts_dir, $this->dir_defaults, 'The fonts_dir() method should return the default values.' ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php deleted file mode 100644 index 1200200d7160b2..00000000000000 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php +++ /dev/null @@ -1,18 +0,0 @@ -assertStringEndsWith( '/wp-content/fonts', WP_Font_Library::get_fonts_dir() ); - } -} diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php deleted file mode 100644 index 29d481d8afd6bc..00000000000000 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php +++ /dev/null @@ -1,32 +0,0 @@ - '/abc', - 'basedir' => '/any/path', - 'baseurl' => 'http://example.com/an/arbitrary/url', - 'path' => '/any/path/abc', - 'url' => 'http://example.com/an/arbitrary/url/abc', - ); - $expected = array( - 'subdir' => '/fonts', - 'basedir' => WP_CONTENT_DIR, - 'baseurl' => content_url(), - 'path' => path_join( WP_CONTENT_DIR, 'fonts' ), - 'url' => content_url() . '/fonts', - ); - $this->assertSame( $expected, WP_Font_Library::set_upload_dir( $defaults ) ); - } -} From 714e6b107670e5ee4d92b5126f99c54305fee03b Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 10 Jan 2024 16:08:05 +0100 Subject: [PATCH 51/51] Tooltip: no-op when nested inside another Tooltip component (#57202) * Tooltip: no-op when nested inside another Tooltip component * Fix Storybook control type * Use internal components context system * Add Storybook example * Snapshots * Add nested unit test * CHANGELOG * Avoid ESLint disable * Keep nested tooltip storybook example * isNestedInParentTooltip => isNestedInTooltip * toBeInTheDocument => toBeVisible * "inner" => "nested" * Forward rest props to the tooltip instead of the ancor, update snapshots * Format warning message * Add paragraph in docs * Complete comment --- packages/components/CHANGELOG.md | 1 + packages/components/src/tooltip/README.md | 4 ++ packages/components/src/tooltip/index.tsx | 56 ++++++++++++++++--- .../src/tooltip/stories/index.story.tsx | 19 ++++++- .../components/src/tooltip/test/index.tsx | 46 +++++++++++++++ packages/components/src/tooltip/types.ts | 4 ++ 6 files changed, 120 insertions(+), 10 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c7c1a515a64cec..a8dd57900cfae1 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -32,6 +32,7 @@ - `InputControl`, `NumberControl`, `UnitControl`, `SelectControl`, `TreeSelect`: Add `compact` size variant ([#57398](https://github.com/WordPress/gutenberg/pull/57398)). - `ToggleGroupControl`: Update button size in large variant to be 32px ([#57338](https://github.com/WordPress/gutenberg/pull/57338)). - `Tooltip`: improve unit tests ([#57345](https://github.com/WordPress/gutenberg/pull/57345)). +- `Tooltip`: no-op when nested inside other `Tooltip` components ([#57202](https://github.com/WordPress/gutenberg/pull/57202)). ### Experimental diff --git a/packages/components/src/tooltip/README.md b/packages/components/src/tooltip/README.md index 9b214e8fc6b00e..ef2cd35d25543e 100644 --- a/packages/components/src/tooltip/README.md +++ b/packages/components/src/tooltip/README.md @@ -16,6 +16,10 @@ const MyTooltip = () => ( ); ``` +### Nested tooltips + +In case one or more `Tooltip` components are rendered inside another `Tooltip` component, only the tooltip associated to the outermost `Tooltip` component will be rendered in the browser and shown to the user appropriately. The rest of the nested `Tooltip` components will simply no-op and pass-through their anchor. + ## Props The component accepts the following props: diff --git a/packages/components/src/tooltip/index.tsx b/packages/components/src/tooltip/index.tsx index 817d6d18812ee4..1e652d9a42dbb4 100644 --- a/packages/components/src/tooltip/index.tsx +++ b/packages/components/src/tooltip/index.tsx @@ -8,22 +8,37 @@ import * as Ariakit from '@ariakit/react'; * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; -import { Children } from '@wordpress/element'; +import { Children, cloneElement } from '@wordpress/element'; import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ -import type { TooltipProps } from './types'; +import type { TooltipProps, TooltipInternalContext } from './types'; import Shortcut from '../shortcut'; import { positionToPlacement } from '../popover/utils'; +import { + contextConnect, + useContextSystem, + ContextSystemProvider, +} from '../context'; +import type { WordPressComponentProps } from '../context'; /** * Time over anchor to wait before showing tooltip */ export const TOOLTIP_DELAY = 700; -function Tooltip( props: TooltipProps ) { +const CONTEXT_VALUE = { + Tooltip: { + isNestedInTooltip: true, + }, +}; + +function UnconnectedTooltip( + props: WordPressComponentProps< TooltipProps, 'div', false >, + ref: React.ForwardedRef< any > +) { const { children, delay = TOOLTIP_DELAY, @@ -32,7 +47,15 @@ function Tooltip( props: TooltipProps ) { position, shortcut, text, - } = props; + + // From Internal Context system + isNestedInTooltip, + + ...restProps + } = useContextSystem< typeof props & TooltipInternalContext >( + props, + 'Tooltip' + ); const baseId = useInstanceId( Tooltip, 'tooltip' ); const describedById = text || shortcut ? baseId : undefined; @@ -43,7 +66,7 @@ function Tooltip( props: TooltipProps ) { if ( 'development' === process.env.NODE_ENV ) { // eslint-disable-next-line no-console console.error( - 'Tooltip should be called with only a single child element.' + 'wp-components.Tooltip should be called with only a single child element.' ); } } @@ -64,24 +87,37 @@ function Tooltip( props: TooltipProps ) { } computedPlacement = computedPlacement || 'bottom'; - const tooltipStore = Ariakit.useTooltipStore( { + // Removing the `Ariakit` namespace from the hook name allows ESLint to + // properly identify the hook, and apply the correct linting rules. + const useAriakitTooltipStore = Ariakit.useTooltipStore; + const tooltipStore = useAriakitTooltipStore( { placement: computedPlacement, showTimeout: delay, } ); + if ( isNestedInTooltip ) { + return isOnlyChild + ? cloneElement( children, { + ...restProps, + ref, + } ) + : children; + } + return ( - <> + { isOnlyChild ? undefined : children } { isOnlyChild && ( text || shortcut ) && ( ) } - + ); } +export const Tooltip = contextConnect( UnconnectedTooltip, 'Tooltip' ); + export default Tooltip; diff --git a/packages/components/src/tooltip/stories/index.story.tsx b/packages/components/src/tooltip/stories/index.story.tsx index 760f3dcc23e2fd..b006bc03aced96 100644 --- a/packages/components/src/tooltip/stories/index.story.tsx +++ b/packages/components/src/tooltip/stories/index.story.tsx @@ -30,7 +30,7 @@ const meta: Meta< typeof Tooltip > = { 'bottom right', ], }, - shortcut: { control: { type: 'text' } }, + shortcut: { control: { type: 'object' } }, }, parameters: { controls: { expanded: true }, @@ -57,3 +57,20 @@ KeyboardShortcut.args = { ariaLabel: shortcutAriaLabel.primaryShift( ',' ), }, }; + +/** + * In case one or more `Tooltip` components are rendered inside another + * `Tooltip` component, only the tooltip associated to the outermost `Tooltip` + * component will be rendered in the browser and shown to the user + * appropriately. The rest of the nested `Tooltip` components will simply no-op + * and pass-through their anchor. + */ +export const Nested: StoryFn< typeof Tooltip > = Template.bind( {} ); +Nested.args = { + children: ( + + + + ), + text: 'Outer tooltip text', +}; diff --git a/packages/components/src/tooltip/test/index.tsx b/packages/components/src/tooltip/test/index.tsx index cbe144cfa53d4d..ed6f7b5f7b4a14 100644 --- a/packages/components/src/tooltip/test/index.tsx +++ b/packages/components/src/tooltip/test/index.tsx @@ -436,4 +436,50 @@ describe( 'Tooltip', () => { await waitExpectTooltipToHide(); } ); } ); + + describe( 'nested', () => { + it( 'should render the outer tooltip and ignore nested tooltips', async () => { + render( + + + + + + + + ); + + // Hover the anchor. Only the outer tooltip should show. + await hover( + screen.getByRole( 'button', { + name: 'Tooltip anchor', + } ) + ); + + await waitFor( () => + expect( + screen.getByRole( 'tooltip', { name: 'Outer tooltip' } ) + ).toBeVisible() + ); + expect( + screen.queryByRole( 'tooltip', { name: 'Middle tooltip' } ) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole( 'tooltip', { name: 'Inner tooltip' } ) + ).not.toBeInTheDocument(); + expect( + screen.getByRole( 'button', { + description: 'Outer tooltip', + } ) + ).toBeVisible(); + + // Hover outside of the anchor, tooltip should hide + await hoverOutside(); + await waitFor( () => + expect( + screen.queryByRole( 'tooltip', { name: 'Outer tooltip' } ) + ).not.toBeInTheDocument() + ); + } ); + } ); } ); diff --git a/packages/components/src/tooltip/types.ts b/packages/components/src/tooltip/types.ts index 8708ae7005f5b3..3d28a1a0e96c67 100644 --- a/packages/components/src/tooltip/types.ts +++ b/packages/components/src/tooltip/types.ts @@ -59,3 +59,7 @@ export type TooltipProps = { */ text?: string; }; + +export type TooltipInternalContext = { + isNestedInTooltip?: boolean; +};