From eed27811415482448ded186f4d48876728801c88 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 17 Oct 2023 12:25:29 +0300 Subject: [PATCH 01/34] [Data views]: Fix pagination (#55387) --- .../src/components/dataviews/pagination.js | 29 +++++++++++-------- .../src/components/dataviews/text-filter.js | 1 + .../src/components/dataviews/view-actions.js | 2 +- .../src/components/page-pages/index.js | 4 +-- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/edit-site/src/components/dataviews/pagination.js b/packages/edit-site/src/components/dataviews/pagination.js index bd6cb503653b07..2f52d8da637e8c 100644 --- a/packages/edit-site/src/components/dataviews/pagination.js +++ b/packages/edit-site/src/components/dataviews/pagination.js @@ -36,7 +36,7 @@ function PageSizeControl( { view, onChangeView } ) { label: pageSize, } ) ) } onChange={ ( value ) => - onChangeView( { ...view, perPage: value } ) + onChangeView( { ...view, perPage: value, page: 1 } ) } /> ); @@ -50,7 +50,6 @@ function Pagination( { onChangeView, paginationInfo: { totalItems = 0, totalPages }, } ) { - const currentPage = view.page + 1; if ( ! totalItems || ! totalPages ) { return null; } @@ -75,8 +74,8 @@ function Pagination( { '; + style="background: #000" + > + + + + + + + '; $body_content = preg_replace( '/]+>/', $button, $body_content ); diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 2ef602982e57b5..0fde1262fdec2d 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -157,14 +157,28 @@ display: flex; flex-direction: column; + img { + cursor: zoom-in; + } + + img:hover + button { + opacity: 1; + } + button { + opacity: 0; border: none; - background: none; + background: #000; cursor: zoom-in; - width: 100%; - height: 100%; + width: 24px; + height: 24px; position: absolute; z-index: 100; + top: 10px; + right: 10px; + text-align: center; + padding: 0; + border-radius: 10%; &:focus-visible { outline: 5px auto #212121; @@ -172,10 +186,19 @@ outline-offset: 5px; } + &:hover { + cursor: pointer; + opacity: 1; + } + + &:focus { + opacity: 1; + } + &:hover, &:focus, &:not(:hover):not(:active):not(.has-background) { - background: none; + background: #000; border: none; } } diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 3f2242ad737f02..30d1259637e3d9 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -103,12 +103,10 @@ store( context.core.image.lastFocusedElement = window.document.activeElement; context.core.image.scrollDelta = 0; + context.core.image.pointerType = event.pointerType; context.core.image.lightboxEnabled = true; - setStyles( - context, - event.target.previousElementSibling - ); + setStyles( context, context.core.image.imageRef ); context.core.image.scrollTopReset = window.pageYOffset || @@ -137,7 +135,7 @@ store( false ); }, - hideLightbox: async ( { context } ) => { + hideLightbox: async ( { context, event } ) => { context.core.image.hideAnimationEnabled = true; if ( context.core.image.lightboxEnabled ) { // We want to wait until the close animation is completed @@ -154,9 +152,16 @@ store( }, 450 ); context.core.image.lightboxEnabled = false; - context.core.image.lastFocusedElement.focus( { - preventScroll: true, - } ); + + // We want to avoid drawing attention to the button + // after the lightbox closes for mouse and touch users. + // Note that the `event.pointerType` property returns + // as an empty string if a keyboard fired the event. + if ( event.pointerType === '' ) { + context.core.image.lastFocusedElement.focus( { + preventScroll: true, + } ); + } } }, handleKeydown: ( { context, actions, event } ) => { @@ -191,11 +196,12 @@ store( } } }, - handleLoad: ( { state, context, effects, ref } ) => { + // This is fired just by lazily loaded + // images on the page, not all images. + handleLoad: ( { context, effects, ref } ) => { context.core.image.imageLoaded = true; context.core.image.imageCurrentSrc = ref.currentSrc; effects.core.image.setButtonStyles( { - state, context, ref, } ); @@ -258,17 +264,14 @@ store( effects: { core: { image: { - setCurrentSrc: ( { context, ref } ) => { + initOriginImage: ( { context, ref } ) => { + context.core.image.imageRef = ref; if ( ref.complete ) { context.core.image.imageLoaded = true; context.core.image.imageCurrentSrc = ref.currentSrc; } }, initLightbox: async ( { context, ref } ) => { - context.core.image.figureRef = - ref.querySelector( 'figure' ); - context.core.image.imageRef = - ref.querySelector( 'img' ); if ( context.core.image.lightboxEnabled ) { const focusableElements = ref.querySelectorAll( focusableSelectors ); @@ -279,10 +282,17 @@ store( focusableElements.length - 1 ]; - ref.querySelector( '.close-button' ).focus(); + // We want to avoid drawing unnecessary attention to the close + // button for mouse and touch users. Note that even if opening + // the lightbox via keyboard, the event fired is of type + // `pointerEvent`, so we need to rely on the `event.pointerType` + // property, which returns an empty string for keyboard events. + if ( context.core.image.pointerType === '' ) { + ref.querySelector( '.close-button' ).focus(); + } } }, - setButtonStyles: ( { state, context, ref } ) => { + setButtonStyles: ( { context, ref } ) => { const { naturalWidth, naturalHeight, @@ -291,54 +301,71 @@ store( } = ref; // If the image isn't loaded yet, we can't - // calculate how big the button should be. + // calculate where the button should be. if ( naturalWidth === 0 || naturalHeight === 0 ) { return; } - // Subscribe to the window dimensions so we can - // recalculate the styles if the window is resized. - if ( - ( state.core.image.windowWidth || - state.core.image.windowHeight ) && - context.core.image.scaleAttr === 'contain' - ) { - // In the case of an image with object-fit: contain, the - // size of the img element can be larger than the image itself, - // so we need to calculate the size of the button to match. + const figure = ref.parentElement; + const figureWidth = ref.parentElement.clientWidth; + + // We need special handling for the height because + // a caption will cause the figure to be taller than + // the image, which means we need to account for that + // when calculating the placement of the button in the + // top right corner of the image. + let figureHeight = ref.parentElement.clientHeight; + const caption = figure.querySelector( 'figcaption' ); + if ( caption ) { + const captionComputedStyle = + window.getComputedStyle( caption ); + figureHeight = + figureHeight - + caption.offsetHeight - + parseFloat( captionComputedStyle.marginTop ) - + parseFloat( captionComputedStyle.marginBottom ); + } + const buttonOffsetTop = figureHeight - offsetHeight; + const buttonOffsetRight = figureWidth - offsetWidth; + + // In the case of an image with object-fit: contain, the + // size of the element can be larger than the image itself, + // so we need to calculate where to place the button. + if ( context.core.image.scaleAttr === 'contain' ) { // Natural ratio of the image. const naturalRatio = naturalWidth / naturalHeight; // Offset ratio of the image. const offsetRatio = offsetWidth / offsetHeight; - if ( naturalRatio > offsetRatio ) { + if ( naturalRatio >= offsetRatio ) { // If it reaches the width first, keep - // the width and recalculate the height. - context.core.image.imageButtonWidth = - offsetWidth; - const buttonHeight = offsetWidth / naturalRatio; - context.core.image.imageButtonHeight = - buttonHeight; + // the width and compute the height. + const referenceHeight = + offsetWidth / naturalRatio; context.core.image.imageButtonTop = - ( offsetHeight - buttonHeight ) / 2; + ( offsetHeight - referenceHeight ) / 2 + + buttonOffsetTop + + 10; + context.core.image.imageButtonRight = + buttonOffsetRight + 10; } else { // If it reaches the height first, keep - // the height and recalculate the width. - context.core.image.imageButtonHeight = - offsetHeight; - const buttonWidth = offsetHeight * naturalRatio; - context.core.image.imageButtonWidth = - buttonWidth; - context.core.image.imageButtonLeft = - ( offsetWidth - buttonWidth ) / 2; + // the height and compute the width. + const referenceWidth = + offsetHeight * naturalRatio; + context.core.image.imageButtonTop = + buttonOffsetTop + 10; + context.core.image.imageButtonRight = + ( offsetWidth - referenceWidth ) / 2 + + buttonOffsetRight + + 10; } } else { - // In all other cases, we can trust that the size of - // the image is the right size for the button as well. - - context.core.image.imageButtonWidth = offsetWidth; - context.core.image.imageButtonHeight = offsetHeight; + context.core.image.imageButtonTop = + buttonOffsetTop + 10; + context.core.image.imageButtonRight = + buttonOffsetRight + 10; } }, setStylesOnResize: ( { state, context, ref } ) => { From 3ba5852f094dd24866ded9a3226ffad6ae1b555e Mon Sep 17 00:00:00 2001 From: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> Date: Wed, 18 Oct 2023 01:16:31 +1100 Subject: [PATCH 06/34] [Edit Widgets] Only suppress admin notices when JS enabled. (#55403) Only suppresses the display of notices on the widget block editor screen if JavaScript is available. --- packages/edit-widgets/src/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index ae850c3bb78fee..72e68e06cfa7e8 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -56,7 +56,7 @@ body.js.widgets-php { // Don't display admin notices on the Widgets screen. // Needs !important because plugins may add inline styles to notices. -.widgets-php .notice { +.js .widgets-php .notice { display: none !important; } From 023c66ff2a7120ec9cc30c4bb782cfa949c7a0b0 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <150562+mcsf@users.noreply.github.com> Date: Tue, 17 Oct 2023 15:36:50 +0100 Subject: [PATCH 07/34] DataViews List: Automatically insert header menu separators (#55412) In HeaderMenu, avoid the very error-prone logic involved in manually adding separators in between top-level items, and instead rely on an introspective component (WithSeparators) to do that task reliably. --- .../src/components/dataviews/view-list.js | 101 ++++++++++-------- 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/packages/edit-site/src/components/dataviews/view-list.js b/packages/edit-site/src/components/dataviews/view-list.js index c8d4ee1efd063d..d675720c86f83f 100644 --- a/packages/edit-site/src/components/dataviews/view-list.js +++ b/packages/edit-site/src/components/dataviews/view-list.js @@ -28,7 +28,7 @@ import { privateApis as componentsPrivateApis, VisuallyHidden, } from '@wordpress/components'; -import { useMemo } from '@wordpress/element'; +import { useMemo, Children, Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -75,53 +75,66 @@ function HeaderMenu( { dataView, header } ) { /> } > - { isSortable && ( - - { Object.entries( sortingItemsInfo ).map( - ( [ direction, info ] ) => ( - } - suffix={ - sortedDirection === direction && ( - - ) - } - onSelect={ ( event ) => { - event.preventDefault(); - if ( sortedDirection === direction ) { - dataView.resetSorting(); - } else { - dataView.setSorting( [ - { - id: header.column.id, - desc: direction === 'desc', - }, - ] ); + + { isSortable && ( + + { Object.entries( sortingItemsInfo ).map( + ( [ direction, info ] ) => ( + } + suffix={ + sortedDirection === direction && ( + + ) } - } } - > - { info.label } - - ) - ) } - - ) } - { isSortable && isHidable && } - { isHidable && ( - } - onSelect={ ( event ) => { - event.preventDefault(); - header.column.getToggleVisibilityHandler()( event ); - } } - > - { __( 'Hide' ) } - - ) } + onSelect={ ( event ) => { + event.preventDefault(); + if ( sortedDirection === direction ) { + dataView.resetSorting(); + } else { + dataView.setSorting( [ + { + id: header.column.id, + desc: direction === 'desc', + }, + ] ); + } + } } + > + { info.label } + + ) + ) } + + ) } + { isHidable && ( + } + onSelect={ ( event ) => { + event.preventDefault(); + header.column.getToggleVisibilityHandler()( event ); + } } + > + { __( 'Hide' ) } + + ) } + ); } + +function WithSeparators( { children } ) { + return Children.toArray( children ) + .filter( Boolean ) + .map( ( child, i ) => ( + + { i > 0 && } + { child } + + ) ); +} + function ViewList( { view, onChangeView, From 8dfa175f1818a604fa830950eaa3056100f0953d Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Tue, 17 Oct 2023 19:58:21 +0300 Subject: [PATCH 08/34] [Data views]: Normalize `render` fields function (#55411) * [Data views]: Normalize `render` fields function * Update packages/edit-site/src/components/dataviews/view-list.js Co-authored-by: Miguel Fonseca <150562+mcsf@users.noreply.github.com> --------- Co-authored-by: Miguel Fonseca <150562+mcsf@users.noreply.github.com> --- .../src/components/dataviews/dataviews.js | 9 ++++++++- .../src/components/dataviews/view-grid.js | 7 ++----- .../src/components/dataviews/view-list.js | 17 +++++------------ .../src/components/page-pages/index.js | 8 ++++---- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/packages/edit-site/src/components/dataviews/dataviews.js b/packages/edit-site/src/components/dataviews/dataviews.js index 755ce8a2ea348d..da0d1735b6a3a6 100644 --- a/packages/edit-site/src/components/dataviews/dataviews.js +++ b/packages/edit-site/src/components/dataviews/dataviews.js @@ -5,6 +5,7 @@ import { __experimentalVStack as VStack, __experimentalHStack as HStack, } from '@wordpress/components'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -25,6 +26,12 @@ export default function DataViews( { paginationInfo, } ) { const ViewComponent = view.type === 'list' ? ViewList : ViewGrid; + const _fields = useMemo( () => { + return fields.map( ( field ) => ( { + ...field, + render: field.render || field.accessorFn, + } ) ); + }, [ fields ] ); return (
@@ -37,7 +44,7 @@ export default function DataViews( { />
- { ( mediaField && - mediaField.render( { item, view } ) ) || ( + { mediaField?.render( item, view ) || ( { visibleFields.map( ( field ) => (
- { field.render - ? field.render( { item, view } ) - : field.accessorFn( item ) } + { field.render( item, view ) }
) ) } diff --git a/packages/edit-site/src/components/dataviews/view-list.js b/packages/edit-site/src/components/dataviews/view-list.js index d675720c86f83f..ba7e6e30328891 100644 --- a/packages/edit-site/src/components/dataviews/view-list.js +++ b/packages/edit-site/src/components/dataviews/view-list.js @@ -145,18 +145,11 @@ function ViewList( { paginationInfo, } ) { const columns = useMemo( () => { - const _columns = [ - ...fields.map( ( field ) => { - const column = { ...field }; - delete column.render; - column.cell = ( props ) => { - return field.render - ? field.render( { item: props.row.original, view } ) - : field.accessorFn( props.row.original ); - }; - return column; - } ), - ]; + const _columns = fields.map( ( field ) => { + const { render, ...column } = field; + column.cell = ( props ) => render( props.row.original, view ); + return column; + } ); if ( actions?.length ) { _columns.push( { header: { __( 'Actions' ) }, diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 602d14b8ad2e3b..502e03f49b3e88 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -89,7 +89,7 @@ export default function PagePages() { id: 'featured-image', header: __( 'Featured Image' ), accessorFn: ( page ) => page.featured_media, - render: ( { item, view: currentView } ) => + render: ( item, currentView ) => !! item.featured_media ? ( page.title?.rendered || page.slug, - render: ( { item: page } ) => { + render: ( page ) => { return ( @@ -134,7 +134,7 @@ export default function PagePages() { header: __( 'Author' ), id: 'author', accessorFn: ( page ) => page._embedded?.author[ 0 ]?.name, - render: ( { item } ) => { + render: ( item ) => { const author = item._embedded?.author[ 0 ]; return (
@@ -153,7 +153,7 @@ export default function PagePages() { { header: 'Date', id: 'date', - render: ( { item } ) => { + render: ( item ) => { const formattedDate = dateI18n( getSettings().formats.datetimeAbbreviated, getDate( item.date ) From d590c5ec7830172aa873fe5ff1b8f8e015f35d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 17 Oct 2023 19:08:07 +0200 Subject: [PATCH 09/34] Add author and status filters to the page list (#55270) --- .../src/components/dataviews/dataviews.js | 24 +++++--- .../src/components/dataviews/filters.js | 57 +++++++++++++++++++ .../src/components/dataviews/in-filter.js | 47 +++++++++++++++ .../src/components/dataviews/pagination.js | 2 +- .../src/components/dataviews/style.scss | 2 +- .../src/components/dataviews/text-filter.js | 9 ++- .../src/components/page-pages/index.js | 41 +++++++++++-- 7 files changed, 165 insertions(+), 17 deletions(-) create mode 100644 packages/edit-site/src/components/dataviews/filters.js create mode 100644 packages/edit-site/src/components/dataviews/in-filter.js diff --git a/packages/edit-site/src/components/dataviews/dataviews.js b/packages/edit-site/src/components/dataviews/dataviews.js index da0d1735b6a3a6..23e0aba02aa400 100644 --- a/packages/edit-site/src/components/dataviews/dataviews.js +++ b/packages/edit-site/src/components/dataviews/dataviews.js @@ -13,7 +13,7 @@ import { useMemo } from '@wordpress/element'; import ViewList from './view-list'; import Pagination from './pagination'; import ViewActions from './view-actions'; -import TextFilter from './text-filter'; +import Filters from './filters'; import { ViewGrid } from './view-grid'; export default function DataViews( { @@ -35,13 +35,21 @@ export default function DataViews( { return (
- - - + + + + + + + { + if ( ! field.filters ) { + return; + } + + field.filters.forEach( ( f ) => { + filters[ f.id ] = { type: f.type }; + } ); + } ); + + return ( + view.visibleFilters?.map( ( filterName ) => { + const filter = filters[ filterName ]; + + if ( ! filter ) { + return null; + } + + if ( filter.type === 'search' ) { + return ( + + ); + } + if ( filter.type === 'enumeration' ) { + return ( + + ); + } + + return null; + } ) || __( 'No filters available' ) + ); +} diff --git a/packages/edit-site/src/components/dataviews/in-filter.js b/packages/edit-site/src/components/dataviews/in-filter.js new file mode 100644 index 00000000000000..0b62b59a3028b0 --- /dev/null +++ b/packages/edit-site/src/components/dataviews/in-filter.js @@ -0,0 +1,47 @@ +/** + * WordPress dependencies + */ +import { + __experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, + SelectControl, +} from '@wordpress/components'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { cleanEmptyObject } = unlock( blockEditorPrivateApis ); + +export default ( { id, fields, view, onChangeView } ) => { + const field = fields.find( ( f ) => f.id === id ); + + return ( + + { field.header + ':' } + + } + options={ field?.elements || [] } + onChange={ ( value ) => { + if ( value === '' ) { + value = undefined; + } + + onChangeView( ( currentView ) => ( { + ...currentView, + filters: cleanEmptyObject( { + ...currentView.filters, + [ id ]: value, + } ), + } ) ); + } } + /> + ); +}; diff --git a/packages/edit-site/src/components/dataviews/pagination.js b/packages/edit-site/src/components/dataviews/pagination.js index 2f52d8da637e8c..5fbe4669768e8b 100644 --- a/packages/edit-site/src/components/dataviews/pagination.js +++ b/packages/edit-site/src/components/dataviews/pagination.js @@ -25,7 +25,7 @@ function PageSizeControl( { view, onChangeView } ) { prefix={ { label } diff --git a/packages/edit-site/src/components/dataviews/style.scss b/packages/edit-site/src/components/dataviews/style.scss index 1b27c0833c836c..ff3bbbbff5db02 100644 --- a/packages/edit-site/src/components/dataviews/style.scss +++ b/packages/edit-site/src/components/dataviews/style.scss @@ -30,7 +30,7 @@ } } -.dataviews__per-page-control-prefix { +.dataviews__select-control-prefix { color: $gray-700; text-wrap: nowrap; } diff --git a/packages/edit-site/src/components/dataviews/text-filter.js b/packages/edit-site/src/components/dataviews/text-filter.js index 31eb5b25e91aa2..4791bc1357e8df 100644 --- a/packages/edit-site/src/components/dataviews/text-filter.js +++ b/packages/edit-site/src/components/dataviews/text-filter.js @@ -10,9 +10,9 @@ import { SearchControl } from '@wordpress/components'; */ import useDebouncedInput from '../../utils/use-debounced-input'; -export default function TextFilter( { view, onChangeView } ) { +export default function TextFilter( { id, view, onChangeView } ) { const [ search, setSearch, debouncedSearch ] = useDebouncedInput( - view.search + view.filters[ id ] ); const onChangeViewRef = useRef( onChangeView ); useEffect( () => { @@ -21,8 +21,11 @@ export default function TextFilter( { view, onChangeView } ) { useEffect( () => { onChangeViewRef.current( ( currentView ) => ( { ...currentView, - search: debouncedSearch, page: 1, + filters: { + ...currentView.filters, + [ id ]: debouncedSearch, + }, } ) ); }, [ debouncedSearch ] ); const searchLabel = __( 'Filter list' ); diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 502e03f49b3e88..0d14a5ffda4d42 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -32,13 +32,17 @@ const defaultConfigPerViewType = { export default function PagePages() { const [ view, setView ] = useState( { type: 'list', - search: '', + filters: { + search: '', + status: 'publish, draft', + }, page: 1, perPage: 5, sort: { field: 'date', direction: 'desc', }, + visibleFilters: [ 'search', 'author', 'status' ], // All fields are visible by default, so it's // better to keep track of the hidden ones. hiddenFields: [ 'date', 'featured-image' ], @@ -63,8 +67,7 @@ export default function PagePages() { _embed: 'author', order: view.sort?.direction, orderby: view.sort?.field, - search: view.search, - status: [ 'publish', 'draft' ], + ...view.filters, } ), [ view ] ); @@ -75,6 +78,10 @@ export default function PagePages() { totalPages, } = useEntityRecords( 'postType', 'page', queryArgs ); + const { records: authors } = useEntityRecords( 'root', 'user', { + who: 'authors', + } ); + const paginationInfo = useMemo( () => ( { totalItems, @@ -126,6 +133,7 @@ export default function PagePages() { ); }, + filters: [ { id: 'search', type: 'search' } ], maxWidth: 400, sortingFn: 'alphanumeric', enableHiding: false, @@ -142,12 +150,37 @@ export default function PagePages() { ); }, + filters: [ { id: 'author', type: 'enumeration' } ], + elements: [ + { + value: '', + label: __( 'All' ), + }, + ...( authors?.map( ( { id, name } ) => ( { + value: id, + label: name, + } ) ) || [] ), + ], }, { header: __( 'Status' ), id: 'status', accessorFn: ( page ) => postStatuses[ page.status ] ?? page.status, + filters: [ { type: 'enumeration', id: 'status' } ], + elements: [ + { label: __( 'All' ), value: 'publish,draft' }, + ...( ( postStatuses && + Object.entries( postStatuses ) + .filter( ( [ slug ] ) => + [ 'publish', 'draft' ].includes( slug ) + ) + .map( ( [ slug, name ] ) => ( { + value: slug, + label: name, + } ) ) ) || + [] ), + ], enableSorting: false, }, { @@ -163,7 +196,7 @@ export default function PagePages() { enableSorting: false, }, ], - [ postStatuses ] + [ postStatuses, authors ] ); const trashPostAction = useTrashPostAction(); From a99f3264efa305b018aeb34785706b023f1acf2c Mon Sep 17 00:00:00 2001 From: Nick Diego Date: Tue, 17 Oct 2023 14:18:05 -0500 Subject: [PATCH 10/34] Add "Get started with create-block" doc to the Getting Started section (#55373) * Add Get started with create-block doc. * Fix typo. * Address feedback and provide more context. --- .../devenv/get-started-with-create-block.md | 95 +++++++++++++++++++ docs/manifest.json | 6 ++ docs/toc.json | 3 + 3 files changed, 104 insertions(+) create mode 100644 docs/getting-started/devenv/get-started-with-create-block.md diff --git a/docs/getting-started/devenv/get-started-with-create-block.md b/docs/getting-started/devenv/get-started-with-create-block.md new file mode 100644 index 00000000000000..8b3a7b5867476f --- /dev/null +++ b/docs/getting-started/devenv/get-started-with-create-block.md @@ -0,0 +1,95 @@ +# Get started with create-block + +Custom blocks for the Block Editor in WordPress are typically registered using plugins and are defined through a specific set of files. The [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package is an officially supported tool to scaffold the structure of files needed to create and register a block. It generates all the necessary code to start a project and integrates a modern JavaScript build setup (using [`wp-scripts`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts.md)) with no configuration required. + +The package is designed to help developers quickly set up a block development environment following WordPress best practices. + +## Quick Start + +### Installation + +Start by ensuring you have Node.js and `npm` installed on your computer. Review the [Node.js development environment](https://developer.wordpress.org/block-editor/getting-started/devenv/nodejs-development-environment/) guide if not. + +You can use `create-block` to scaffold a block just about anywhere and then [use `wp-env`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/) from the inside of the generated plugin folder. This will create a local WordPress development environment with your new block plugin installed and activated. + +If you have your own [local WordPress development environment](https://developer.wordpress.org/block-editor/getting-started/devenv/#local-wordpress-environment) already set up, navigate to the `plugins/` folder using the terminal. + +Run the following command to scaffold an example block plugin: + +```bash +npx @wordpress/create-block@latest todo-list +cd todo-list +``` + +The `slug` provided (`todo-list`) defines the folder name for the scaffolded plugin and the internal block name. + +Navigate to the Plugins page of our local WordPress installation and activate the "Todo List" plugin. The example block will then be available in the Editor. + +### Basic usage + +The `create-block` assumes you will use modern JavaScript (ESNext and JSX) to build your block. This requires a build step to compile the code into a format that browsers can understand. Luckily, the `wp-scripts` package handles this process for you. Refer to the [Get started with wp-scripts](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts) for an introduction to this package. + + When `create-block` scaffolds the block, it installs `wp-scripts` and adds the most common scripts to the block's `package.json` file. By default, those include: + +```json +{ + "scripts": { + "build": "wp-scripts build", + "format": "wp-scripts format", + "lint:css": "wp-scripts lint-style", + "lint:js": "wp-scripts lint-js", + "packages-update": "wp-scripts packages-update", + "plugin-zip": "wp-scripts plugin-zip", + "start": "wp-scripts start" + } +} +``` + +These scripts can then be run using the command `npm run {script name}`. The two scripts you will use most often are `start` and `build` since they handle the build step. + +When working on your block, use the `npm run start` command. This will start a development server and automatically rebuild the block whenever any code change is detected. + +When you are ready to deploy your block, use the `npm run build` command. This optimizes your code and makes it production-ready. + +See the `wp-scripts` [package documentation](https://developer.wordpress.org/block-editor/packages/packages-scripts/) for more details about each available script. + +## Alternate implementations + +### Interactive mode + +For developers who prefer a more guided experience, the `create-block package` provides an interactive mode. Instead of manually specifying all options upfront, like the `slug` in the above example, this mode will prompt you for inputs step-by-step. + +To use this mode, run the command: + +```bash +npx @wordpress/create-block@latest +``` + +Follow the prompts to configure your block settings interactively. + +### Quick start mode using options + +If you're already familiar with the `create-block` [options](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/#options) and want a more streamlined setup, you can use quick start mode. This allows you to pass specific options directly in the command line, eliminating the need for interactive prompts. + +For instance, to quickly create a block named "my-block" with a namespace of "my-plugin" that is a Dynamic block, use this command: + +```bash +npx @wordpress/create-block@latest --namespace="my-plugin" --slug="my-block" --variant="dynamic" +``` + +### Using templates + +The `create-block` package also supports the use of templates, enabling you to create blocks based on predefined configurations and structures. This is especially useful when you have a preferred block structure or when you're building multiple blocks with similar configurations. + +To use a template, specify the `--template` option followed by the template name or path: +```bash +npx @wordpress/create-block --template="my-custom-template" +``` + +Templates must be set up in advance so the `create-block` package knows where to find them. Learn more in the `create-block` [documentation](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/#template), and review the [External Project Templates](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/packages-create-block-external-template/) guide. + +## Additional resources + +- [Using the create-block tool](https://learn.wordpress.org/tutorial/using-the-create-block-tool/) (Learn WordPress tutorial) +- [@wordpress/create-block](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) (Official documentation) +- [@wordpress/scripts](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) (Official documentation) diff --git a/docs/manifest.json b/docs/manifest.json index 297f30fda0b6d6..54e8312e0c82a8 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -29,6 +29,12 @@ "markdown_source": "../docs/getting-started/devenv/get-started-with-wp-env.md", "parent": "devenv" }, + { + "title": "Get started with create-block", + "slug": "get-started-with-create-block", + "markdown_source": "../docs/getting-started/devenv/get-started-with-create-block.md", + "parent": "devenv" + }, { "title": "Create a Block Tutorial", "slug": "create-block", diff --git a/docs/toc.json b/docs/toc.json index 1629a22b6753f5..d785d4867b46f9 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -11,6 +11,9 @@ }, { "docs/getting-started/devenv/get-started-with-wp-env.md": [] + }, + { + "docs/getting-started/devenv/get-started-with-create-block.md": [] } ] }, From 2321a48ab4d1c698bed1c8f508adbb8596b957b1 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 18 Oct 2023 07:29:26 +0100 Subject: [PATCH 11/34] DataViews: Fix the actions menu in the list view (#55426) --- packages/edit-site/src/components/dataviews/view-list.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/dataviews/view-list.js b/packages/edit-site/src/components/dataviews/view-list.js index ba7e6e30328891..cc54ceccf15b39 100644 --- a/packages/edit-site/src/components/dataviews/view-list.js +++ b/packages/edit-site/src/components/dataviews/view-list.js @@ -155,14 +155,19 @@ function ViewList( { header: { __( 'Actions' ) }, id: 'actions', cell: ( props ) => { - return ; + return ( + + ); }, enableHiding: false, } ); } return _columns; - }, [ fields, actions ] ); + }, [ fields, actions, view ] ); const columnVisibility = useMemo( () => { if ( ! view.hiddenFields?.length ) { From 2525a641f13fd8b0e1524e1c8a1ce152678770d5 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:23:29 +0300 Subject: [PATCH 12/34] Try to fix flaky synced pattern test (#55406) --- test/e2e/specs/editor/various/writing-flow.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/editor/various/writing-flow.spec.js b/test/e2e/specs/editor/various/writing-flow.spec.js index a772bd91276c21..a248915a21fcba 100644 --- a/test/e2e/specs/editor/various/writing-flow.spec.js +++ b/test/e2e/specs/editor/various/writing-flow.spec.js @@ -1128,8 +1128,10 @@ test.describe( 'Writing Flow (@firefox, @webkit)', () => { await page.keyboard.press( 'Enter' ); await expect( - editor.canvas.locator( '[data-type="core/block"]' ) - ).toBeFocused(); + editor.canvas.locator( + '[data-type="core/block"] [data-type="core/paragraph"]' + ) + ).toBeVisible(); await editor.insertBlock( { name: 'core/paragraph' } ); From 418a3fc15594126914562c0e18c4993a8822b8b2 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 18 Oct 2023 10:28:55 +0300 Subject: [PATCH 13/34] [Data views]: Rename accessorFn to getValue (#55434) --- .../src/components/dataviews/dataviews.js | 2 +- .../src/components/dataviews/view-grid.js | 4 ++-- .../src/components/dataviews/view-list.js | 8 +++++-- .../src/components/page-pages/index.js | 24 +++++++++---------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/edit-site/src/components/dataviews/dataviews.js b/packages/edit-site/src/components/dataviews/dataviews.js index 23e0aba02aa400..b488836a91f3eb 100644 --- a/packages/edit-site/src/components/dataviews/dataviews.js +++ b/packages/edit-site/src/components/dataviews/dataviews.js @@ -29,7 +29,7 @@ export default function DataViews( { const _fields = useMemo( () => { return fields.map( ( field ) => ( { ...field, - render: field.render || field.accessorFn, + render: field.render || field.getValue, } ) ); }, [ fields ] ); return ( diff --git a/packages/edit-site/src/components/dataviews/view-grid.js b/packages/edit-site/src/components/dataviews/view-grid.js index 353c2de39a1d9e..30a36b68e2c09e 100644 --- a/packages/edit-site/src/components/dataviews/view-grid.js +++ b/packages/edit-site/src/components/dataviews/view-grid.js @@ -29,7 +29,7 @@ export function ViewGrid( { data, fields, view, actions } ) { return (
- { mediaField?.render( item, view ) || ( + { mediaField?.render( { item, view } ) || ( { visibleFields.map( ( field ) => (
- { field.render( item, view ) } + { field.render( { item, view } ) }
) ) } diff --git a/packages/edit-site/src/components/dataviews/view-list.js b/packages/edit-site/src/components/dataviews/view-list.js index cc54ceccf15b39..22b9a059c2f1bf 100644 --- a/packages/edit-site/src/components/dataviews/view-list.js +++ b/packages/edit-site/src/components/dataviews/view-list.js @@ -146,8 +146,12 @@ function ViewList( { } ) { const columns = useMemo( () => { const _columns = fields.map( ( field ) => { - const { render, ...column } = field; - column.cell = ( props ) => render( props.row.original, view ); + const { render, getValue, ...column } = field; + column.cell = ( props ) => + render( { item: props.row.original, view } ); + if ( getValue ) { + column.accessorFn = ( item ) => getValue( { item } ); + } return column; } ); if ( actions?.length ) { diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 0d14a5ffda4d42..4b7fe8228a53a9 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -95,8 +95,8 @@ export default function PagePages() { { id: 'featured-image', header: __( 'Featured Image' ), - accessorFn: ( page ) => page.featured_media, - render: ( item, currentView ) => + getValue: ( { item } ) => item.featured_media, + render: ( { item, view: currentView } ) => !! item.featured_media ? ( page.title?.rendered || page.slug, - render: ( page ) => { + getValue: ( { item } ) => item.title?.rendered || item.slug, + render: ( { item } ) => { return ( { decodeEntities( - page.title?.rendered || page.slug + item.title?.rendered || item.slug ) || __( '(no title)' ) } @@ -141,8 +141,8 @@ export default function PagePages() { { header: __( 'Author' ), id: 'author', - accessorFn: ( page ) => page._embedded?.author[ 0 ]?.name, - render: ( item ) => { + getValue: ( { item } ) => item._embedded?.author[ 0 ]?.name, + render: ( { item } ) => { const author = item._embedded?.author[ 0 ]; return ( @@ -165,8 +165,8 @@ export default function PagePages() { { header: __( 'Status' ), id: 'status', - accessorFn: ( page ) => - postStatuses[ page.status ] ?? page.status, + getValue: ( { item } ) => + postStatuses[ item.status ] ?? item.status, filters: [ { type: 'enumeration', id: 'status' } ], elements: [ { label: __( 'All' ), value: 'publish,draft' }, @@ -186,7 +186,7 @@ export default function PagePages() { { header: 'Date', id: 'date', - render: ( item ) => { + render: ( { item } ) => { const formattedDate = dateI18n( getSettings().formats.datetimeAbbreviated, getDate( item.date ) From af34a42bd3fa1045fcd4b9d7542f451aa27ce620 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Wed, 18 Oct 2023 10:46:35 +0200 Subject: [PATCH 14/34] Cover: Add aria-label to fixed and repeated image backgrounds (#50990) * Cover: Add aria-label to fixed and repeated backgrounds * Update deprecated.js * Update duotone and fixed background fixture * Add a new fixture for a cover block with fixed background and alternative text as aria-label. * Update packages/block-library/src/cover/deprecated.js * Oops: Hide the alt text field for video cover blocks * Try to fix merge conflicts * Try to fix merge conflicts * Use constants for attributes and supports --- packages/block-library/src/cover/block.json | 3 - .../block-library/src/cover/deprecated.js | 152 +++++++++++++++++- .../block-library/src/cover/edit/index.js | 3 +- .../src/cover/edit/inspector-controls.js | 44 +++-- packages/block-library/src/cover/save.js | 3 +- .../core__cover__alt-fixed-background.html | 5 + .../core__cover__alt-fixed-background.json | 35 ++++ ...e__cover__alt-fixed-background.parsed.json | 37 +++++ ...over__alt-fixed-background.serialized.html | 5 + ...core__cover__duotone-fixed-background.html | 2 +- ...over__duotone-fixed-background.parsed.json | 4 +- ...__duotone-fixed-background.serialized.html | 2 +- 12 files changed, 260 insertions(+), 35 deletions(-) create mode 100644 test/integration/fixtures/blocks/core__cover__alt-fixed-background.html create mode 100644 test/integration/fixtures/blocks/core__cover__alt-fixed-background.json create mode 100644 test/integration/fixtures/blocks/core__cover__alt-fixed-background.parsed.json create mode 100644 test/integration/fixtures/blocks/core__cover__alt-fixed-background.serialized.html diff --git a/packages/block-library/src/cover/block.json b/packages/block-library/src/cover/block.json index c186a2416f5c9e..d2c55dd26b4d74 100644 --- a/packages/block-library/src/cover/block.json +++ b/packages/block-library/src/cover/block.json @@ -19,9 +19,6 @@ }, "alt": { "type": "string", - "source": "attribute", - "selector": "img", - "attribute": "alt", "default": "" }, "hasParallax": { diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index fc15cb4ac46d49..e2e773cace1426 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -244,6 +244,156 @@ const v12BlockSupports = { }, }; +// Deprecation for blocks that does not have the aria-label when the image background is fixed or repeated. +const v13 = { + attributes: v12BlockAttributes, + supports: v12BlockSupports, + save( { attributes } ) { + const { + backgroundType, + gradient, + contentPosition, + customGradient, + customOverlayColor, + dimRatio, + focalPoint, + useFeaturedImage, + hasParallax, + isDark, + isRepeated, + overlayColor, + url, + alt, + id, + minHeight: minHeightProp, + minHeightUnit, + tagName: Tag, + } = attributes; + const overlayColorClass = getColorClassName( + 'background-color', + overlayColor + ); + const gradientClass = __experimentalGetGradientClass( gradient ); + const minHeight = + minHeightProp && minHeightUnit + ? `${ minHeightProp }${ minHeightUnit }` + : minHeightProp; + + const isImageBackground = IMAGE_BACKGROUND_TYPE === backgroundType; + const isVideoBackground = VIDEO_BACKGROUND_TYPE === backgroundType; + + const isImgElement = ! ( hasParallax || isRepeated ); + + const style = { + minHeight: minHeight || undefined, + }; + + const bgStyle = { + backgroundColor: ! overlayColorClass + ? customOverlayColor + : undefined, + background: customGradient ? customGradient : undefined, + }; + + const objectPosition = + // prettier-ignore + focalPoint && isImgElement + ? mediaPosition(focalPoint) + : undefined; + + const backgroundImage = url ? `url(${ url })` : undefined; + + const backgroundPosition = mediaPosition( focalPoint ); + + const classes = classnames( + { + 'is-light': ! isDark, + 'has-parallax': hasParallax, + 'is-repeated': isRepeated, + 'has-custom-content-position': + ! isContentPositionCenter( contentPosition ), + }, + getPositionClassName( contentPosition ) + ); + + const imgClasses = classnames( + 'wp-block-cover__image-background', + id ? `wp-image-${ id }` : null, + { + 'has-parallax': hasParallax, + 'is-repeated': isRepeated, + } + ); + + const gradientValue = gradient || customGradient; + + return ( + +
+ ) ) } + { isVideoBackground && url && ( +
+ + ); + }, +}; + // Deprecation for blocks to prevent auto overlay color from overriding previously set values. const v12 = { attributes: v12BlockAttributes, @@ -1673,4 +1823,4 @@ const v1 = { }, }; -export default [ v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1 ]; +export default [ v13, v12, v11, v10, v9, v8, v7, v6, v5, v4, v3, v2, v1 ]; diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js index 512794ee3f0ec8..5ff6f8a02ac140 100644 --- a/packages/block-library/src/cover/edit/index.js +++ b/packages/block-library/src/cover/edit/index.js @@ -534,7 +534,8 @@ function CoverEdit( { ) : (
) } - { ! useFeaturedImage && - url && - isImageBackground && - isImgElement && ( - - setAttributes( { alt: newAlt } ) - } - help={ - <> - - { __( - 'Describe the purpose of the image.' - ) } - -
+ { ! useFeaturedImage && url && ! isVideoBackground && ( + + setAttributes( { alt: newAlt } ) + } + help={ + <> + { __( - 'Leave empty if decorative.' + 'Describe the purpose of the image.' ) } - - } - /> - ) } + +
+ { __( 'Leave empty if decorative.' ) } + + } + /> + ) }