From 0444bed918c240730bf863904e5a3ce9b8a65af5 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 26 Nov 2024 13:27:27 +0100 Subject: [PATCH 01/10] Support mixed render for bulk editing --- .../src/components/dataform/index.tsx | 8 ++++- .../dataforms-layouts/data-form-layout.tsx | 12 +++++-- .../dataviews/src/dataforms-layouts/index.tsx | 2 ++ .../src/dataforms-layouts/panel/index.tsx | 18 +++++++++-- packages/dataviews/src/types.ts | 7 +++++ .../src/components/post-edit/index.js | 31 +++++++++++++++++-- 6 files changed, 69 insertions(+), 9 deletions(-) diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index b359ddba74381..5412f0ab90ee6 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -16,6 +16,7 @@ export default function DataForm< Item >( { form, fields, onChange, + isBulkEditing, }: DataFormProps< Item > ) { const normalizedFields = useMemo( () => normalizeFields( fields ), @@ -28,7 +29,12 @@ export default function DataForm< Item >( { return ( - + ); } diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx index 08cc47f569eaf..bef533524e43e 100644 --- a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx +++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx @@ -18,6 +18,7 @@ export function DataFormLayout< Item >( { form, onChange, children, + isBulkEditing, }: { data: Item; form: Form; @@ -31,6 +32,7 @@ export function DataFormLayout< Item >( { } ) => React.JSX.Element | null, field: FormField ) => React.JSX.Element; + isBulkEditing?: boolean; } ) { const { fields: fieldDefinitions } = useContext( DataFormContext ); @@ -50,10 +52,13 @@ export function DataFormLayout< Item >( { return ( { normalizedFormFields.map( ( formField ) => { - const FieldLayout = getFormFieldLayout( formField.layout ) - ?.component; + const formFieldLayout = getFormFieldLayout( formField.layout ); + const FieldLayout = formFieldLayout?.component; - if ( ! FieldLayout ) { + if ( + ! FieldLayout || + ( isBulkEditing && ! formFieldLayout?.supportsBulk ) + ) { return null; } @@ -79,6 +84,7 @@ export function DataFormLayout< Item >( { data={ data } field={ formField } onChange={ onChange } + isBulkEditing={ isBulkEditing } /> ); } ) } diff --git a/packages/dataviews/src/dataforms-layouts/index.tsx b/packages/dataviews/src/dataforms-layouts/index.tsx index 5e4f3617d9c7d..bed869baa4c89 100644 --- a/packages/dataviews/src/dataforms-layouts/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/index.tsx @@ -8,10 +8,12 @@ const FORM_FIELD_LAYOUTS = [ { type: 'regular', component: FormRegularField, + supportsBulk: false, }, { type: 'panel', component: FormPanelField, + supportsBulk: true, }, ]; diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index 269b2bb418a85..b8dbfcb773997 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -10,7 +10,7 @@ import { Button, } from '@wordpress/components'; import { sprintf, __, _x } from '@wordpress/i18n'; -import { useState, useMemo, useContext } from '@wordpress/element'; +import { useState, useMemo, useContext, Fragment } from '@wordpress/element'; import { closeSmall } from '@wordpress/icons'; /** @@ -66,6 +66,7 @@ function PanelDropdown< Item >( { data, onChange, field, + isBulkEditing, }: { fieldDefinition: NormalizedField< Item >; popoverAnchor: HTMLElement | null; @@ -73,6 +74,7 @@ function PanelDropdown< Item >( { data: Item; onChange: ( value: any ) => void; field: FormField; + isBulkEditing?: boolean; } ) { const fieldLabel = isCombinedField( field ) ? field.label @@ -111,6 +113,10 @@ function PanelDropdown< Item >( { [ popoverAnchor ] ); + const fieldValue = fieldDefinition.getValue( { item: data } ); + const showMixedValue = + isBulkEditing && ( fieldValue === undefined || fieldValue === '' ); + return ( ( { ) } onClick={ onToggle } > - + { showMixedValue ? ( + <>Mixed + ) : ( + + ) } ) } renderContent={ ( { onClose } ) => ( @@ -171,6 +181,7 @@ export default function FormPanelField< Item >( { data, field, onChange, + isBulkEditing, }: FieldLayoutProps< Item > ) { const { fields } = useContext( DataFormContext ); const fieldDefinition = fields.find( ( fieldDef ) => { @@ -221,6 +232,7 @@ export default function FormPanelField< Item >( { data={ data } onChange={ onChange } labelPosition={ labelPosition } + isBulkEditing={ isBulkEditing } /> @@ -237,6 +249,7 @@ export default function FormPanelField< Item >( { data={ data } onChange={ onChange } labelPosition={ labelPosition } + isBulkEditing={ isBulkEditing } /> ); @@ -259,6 +272,7 @@ export default function FormPanelField< Item >( { data={ data } onChange={ onChange } labelPosition={ labelPosition } + isBulkEditing={ isBulkEditing } /> diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 0bce8b8cf2c08..d77a1357f9d96 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -158,6 +158,11 @@ export type Field< Item > = { * Defaults to `item[ field.id ]`. */ getValue?: ( args: { item: Item } ) => any; + + /** + * Whether the action can be used as a bulk action. + */ + supportsBulk?: boolean; }; export type NormalizedField< Item > = Field< Item > & { @@ -568,6 +573,7 @@ export interface DataFormProps< Item > { fields: Field< Item >[]; form: Form; onChange: ( value: Record< string, any > ) => void; + isBulkEditing?: boolean; } export interface FieldLayoutProps< Item > { @@ -575,4 +581,5 @@ export interface FieldLayoutProps< Item > { field: FormField; onChange: ( value: any ) => void; hideLabelFromVision?: boolean; + isBulkEditing?: boolean; } diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 3e75ef71d1ac9..2cf5996fab97c 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -32,7 +32,7 @@ const fieldsWithBulkEditSupport = [ function PostEditForm( { postType, postId } ) { const ids = useMemo( () => postId.split( ',' ), [ postId ] ); - const { record } = useSelect( + const { record, records } = useSelect( ( select ) => { return { record: @@ -43,6 +43,16 @@ function PostEditForm( { postType, postId } ) { ids[ 0 ] ) : null, + records: + ids.length > 1 + ? ids.map( ( id ) => + select( coreDataStore ).getEditedEntityRecord( + 'postType', + postType, + id + ) + ) + : null, }; }, [ postType, ids ] @@ -120,8 +130,22 @@ function PostEditForm( { postType, postId } ) { } }; useEffect( () => { - setMultiEdits( {} ); - }, [ ids ] ); + if ( records && records.length > 1 ) { + const intersectingValues = {}; + const keys = Object.keys( records[ 0 ] ); + for ( const key of keys ) { + const [ firstRecord, ...remainingRecords ] = records; + const intersects = remainingRecords.every( ( item ) => { + return item[ key ] === firstRecord[ key ]; + } ); + if ( intersects ) { + intersectingValues[ key ] = firstRecord[ key ]; + } + } + + setMultiEdits( intersectingValues ); + } + }, [ records ] ); return ( @@ -130,6 +154,7 @@ function PostEditForm( { postType, postId } ) { ) } 1 } fields={ fields } form={ form } onChange={ onChange } From 3e5e62ccb88ad02418c54b07e651cb3c8715a010 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 26 Nov 2024 17:20:21 +0100 Subject: [PATCH 02/10] Allow array of items being passed into form for bulk editing --- .../src/components/dataform/index.tsx | 34 ++++++++++++++++-- .../dataforms-layouts/data-form-layout.tsx | 35 +++++++++++++++++-- .../dataviews/src/dataforms-layouts/index.tsx | 2 +- .../src/dataforms-layouts/panel/index.tsx | 3 +- .../src/dataforms-layouts/regular/index.tsx | 2 +- packages/dataviews/src/types.ts | 3 +- .../src/components/post-edit/index.js | 33 +++-------------- packages/fields/src/fields/author/index.tsx | 1 + packages/fields/src/fields/date/index.tsx | 1 + packages/fields/src/fields/status/index.tsx | 1 + packages/fields/src/fields/title/index.ts | 1 + 11 files changed, 78 insertions(+), 38 deletions(-) diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index 5412f0ab90ee6..202afc45cbf26 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -11,26 +11,54 @@ import { DataFormProvider } from '../dataform-context'; import { normalizeFields } from '../../normalize-fields'; import { DataFormLayout } from '../../dataforms-layouts/data-form-layout'; -export default function DataForm< Item >( { +/** + * Loops through the list of data items and returns an object with the intersecting ( same ) key and values. + * + * @param data list of items. + */ +function getIntersectingValues< Item extends object >( data: Item[] ): Item { + const intersectingValues = {} as Item; + const keys = Object.keys( data[ 0 ] ) as Array< keyof Item >; + for ( const key of keys ) { + const [ firstRecord, ...remainingRecords ] = data; + const intersects = remainingRecords.every( ( item ) => { + return item[ key ] === firstRecord[ key ]; + } ); + if ( intersects ) { + intersectingValues[ key ] = firstRecord[ key ]; + } + } + return intersectingValues; +} + +export default function DataForm< Item extends object >( { data, form, fields, onChange, - isBulkEditing, }: DataFormProps< Item > ) { const normalizedFields = useMemo( () => normalizeFields( fields ), [ fields ] ); + const flattenedData = useMemo( () => { + if ( Array.isArray( data ) ) { + return getIntersectingValues< Item >( data ); + } + return data; + }, [ data ] ); + if ( ! form.fields ) { return null; } + const isBulkEditing = Array.isArray( data ); + return ( ( { +function doesCombinedFieldSupportBulkEdits< Item >( + combinedField: CombinedFormField, + fieldDefinitions: NormalizedField< Item >[] +): boolean { + return combinedField.children.some( ( child ) => { + const fieldId = typeof child === 'string' ? child : child.id; + + return fieldDefinitions.find( + ( fieldDefinition ) => fieldDefinition.id === fieldId + )?.supportsBulk; + } ); +} + +export function DataFormLayout< Item extends object >( { data, form, onChange, @@ -74,6 +93,18 @@ export function DataFormLayout< Item >( { return null; } + if ( + isBulkEditing && + ( ( isCombinedField( formField ) && + ! doesCombinedFieldSupportBulkEdits( + formField, + fieldDefinitions + ) ) || + ( fieldDefinition && ! fieldDefinition.supportsBulk ) ) + ) { + return null; + } + if ( children ) { return children( FieldLayout, formField ); } diff --git a/packages/dataviews/src/dataforms-layouts/index.tsx b/packages/dataviews/src/dataforms-layouts/index.tsx index bed869baa4c89..2dc2039dc407e 100644 --- a/packages/dataviews/src/dataforms-layouts/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/index.tsx @@ -8,7 +8,7 @@ const FORM_FIELD_LAYOUTS = [ { type: 'regular', component: FormRegularField, - supportsBulk: false, + supportsBulk: true, }, { type: 'panel', diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index b8dbfcb773997..473534c9e7356 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -59,7 +59,7 @@ function DropdownHeader( { ); } -function PanelDropdown< Item >( { +function PanelDropdown< Item extends object >( { fieldDefinition, popoverAnchor, labelPosition = 'side', @@ -158,6 +158,7 @@ function PanelDropdown< Item >( { data={ data } form={ form as Form } onChange={ onChange } + isBulkEditing={ isBulkEditing } > { ( FieldLayout, nestedField ) => ( ( { +export default function FormRegularField< Item extends object >( { data, field, onChange, diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index d77a1357f9d96..71c8806548eaa 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -569,11 +569,10 @@ export type Form = { }; export interface DataFormProps< Item > { - data: Item; + data: Item | Item[]; fields: Field< Item >[]; form: Form; onChange: ( value: Record< string, any > ) => void; - isBulkEditing?: boolean; } export interface FieldLayoutProps< Item > { diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 2cf5996fab97c..284f7b283fa81 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -11,7 +11,7 @@ import { DataForm } from '@wordpress/dataviews'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as coreDataStore } from '@wordpress/core-data'; import { __experimentalVStack as VStack } from '@wordpress/components'; -import { useState, useMemo, useEffect } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** @@ -57,7 +57,6 @@ function PostEditForm( { postType, postId } ) { }, [ postType, ids ] ); - const [ multiEdits, setMultiEdits ] = useState( {} ); const { editEntityRecord } = useDispatch( coreDataStore ); const { fields: _fields } = usePostFields( { postType } ); const fields = useMemo( @@ -98,7 +97,9 @@ function PostEditForm( { postType, postId } ) { ].filter( ( field ) => ids.length === 1 || - fieldsWithBulkEditSupport.includes( field ) + fieldsWithBulkEditSupport.includes( + typeof field === 'string' ? field : field.id + ) ), } ), [ ids ] @@ -121,31 +122,8 @@ function PostEditForm( { postType, postId } ) { edits.password = ''; } editEntityRecord( 'postType', postType, id, edits ); - if ( ids.length > 1 ) { - setMultiEdits( ( prev ) => ( { - ...prev, - ...edits, - } ) ); - } } }; - useEffect( () => { - if ( records && records.length > 1 ) { - const intersectingValues = {}; - const keys = Object.keys( records[ 0 ] ); - for ( const key of keys ) { - const [ firstRecord, ...remainingRecords ] = records; - const intersects = remainingRecords.every( ( item ) => { - return item[ key ] === firstRecord[ key ]; - } ); - if ( intersects ) { - intersectingValues[ key ] = firstRecord[ key ]; - } - } - - setMultiEdits( intersectingValues ); - } - }, [ records ] ); return ( @@ -153,8 +131,7 @@ function PostEditForm( { postType, postId } ) { ) } 1 } + data={ ids.length === 1 ? record : records } fields={ fields } form={ form } onChange={ onChange } diff --git a/packages/fields/src/fields/author/index.tsx b/packages/fields/src/fields/author/index.tsx index 22f9409e05155..08bfa3ca8d87b 100644 --- a/packages/fields/src/fields/author/index.tsx +++ b/packages/fields/src/fields/author/index.tsx @@ -15,6 +15,7 @@ const authorField: Field< BasePostWithEmbeddedAuthor > = { id: 'author', type: 'integer', elements: [], + supportsBulk: true, render: AuthorView, sort: ( a, b, direction ) => { const nameA = a._embedded?.author?.[ 0 ]?.name || ''; diff --git a/packages/fields/src/fields/date/index.tsx b/packages/fields/src/fields/date/index.tsx index 49bd0807de669..c9e1d28a6dba9 100644 --- a/packages/fields/src/fields/date/index.tsx +++ b/packages/fields/src/fields/date/index.tsx @@ -15,6 +15,7 @@ const dateField: Field< BasePost > = { type: 'datetime', label: __( 'Date' ), render: DateView, + supportsBulk: true, }; /** diff --git a/packages/fields/src/fields/status/index.tsx b/packages/fields/src/fields/status/index.tsx index 374277bc7260e..26e3ad22b6e98 100644 --- a/packages/fields/src/fields/status/index.tsx +++ b/packages/fields/src/fields/status/index.tsx @@ -20,6 +20,7 @@ const statusField: Field< BasePost > = { elements: STATUSES, render: StatusView, Edit: 'radio', + supportsBulk: true, enableSorting: false, filterBy: { operators: [ OPERATOR_IS_ANY ], diff --git a/packages/fields/src/fields/title/index.ts b/packages/fields/src/fields/title/index.ts index d8e6f25276d6b..33414ce172ea9 100644 --- a/packages/fields/src/fields/title/index.ts +++ b/packages/fields/src/fields/title/index.ts @@ -19,6 +19,7 @@ const titleField: Field< BasePost > = { getValue: ( { item } ) => getItemTitle( item ), render: TitleView, enableHiding: false, + supportsBulk: true, }; export default titleField; From 7869b30819a17d2114cb292ed991f4d9d092ebf1 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 27 Nov 2024 11:21:43 +0100 Subject: [PATCH 03/10] Add translation for Mixed label --- packages/dataviews/src/dataforms-layouts/panel/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index 473534c9e7356..f3d35edea2805 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -10,7 +10,7 @@ import { Button, } from '@wordpress/components'; import { sprintf, __, _x } from '@wordpress/i18n'; -import { useState, useMemo, useContext, Fragment } from '@wordpress/element'; +import { useState, useMemo, useContext } from '@wordpress/element'; import { closeSmall } from '@wordpress/icons'; /** @@ -145,7 +145,7 @@ function PanelDropdown< Item extends object >( { onClick={ onToggle } > { showMixedValue ? ( - <>Mixed + __( 'Mixed' ) ) : ( ) } From 97ae2b51e3c12f20418fd59ad46e608bad9d1945 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 27 Nov 2024 11:52:40 +0100 Subject: [PATCH 04/10] Default supportsBulk to false --- packages/dataviews/src/normalize-fields.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 2ed87cbe11222..0c1e81518c9b4 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -76,6 +76,7 @@ export function normalizeFields< Item >( sort, isValid, Edit, + supportsBulk: field.supportsBulk ?? false, enableHiding: field.enableHiding ?? true, enableSorting: field.enableSorting ?? true, }; From 489623ad915affea453e5e00a87c7fba559cb1a3 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 27 Nov 2024 11:55:51 +0100 Subject: [PATCH 05/10] Remove field layout supportsBulk config --- .../dataviews/src/dataforms-layouts/data-form-layout.tsx | 9 +++------ packages/dataviews/src/dataforms-layouts/index.tsx | 2 -- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx index 01991a9e085a0..2afa7933c8934 100644 --- a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx +++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx @@ -71,13 +71,10 @@ export function DataFormLayout< Item extends object >( { return ( { normalizedFormFields.map( ( formField ) => { - const formFieldLayout = getFormFieldLayout( formField.layout ); - const FieldLayout = formFieldLayout?.component; + const FieldLayout = getFormFieldLayout( formField.layout ) + ?.component; - if ( - ! FieldLayout || - ( isBulkEditing && ! formFieldLayout?.supportsBulk ) - ) { + if ( ! FieldLayout ) { return null; } diff --git a/packages/dataviews/src/dataforms-layouts/index.tsx b/packages/dataviews/src/dataforms-layouts/index.tsx index 2dc2039dc407e..5e4f3617d9c7d 100644 --- a/packages/dataviews/src/dataforms-layouts/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/index.tsx @@ -8,12 +8,10 @@ const FORM_FIELD_LAYOUTS = [ { type: 'regular', component: FormRegularField, - supportsBulk: true, }, { type: 'panel', component: FormPanelField, - supportsBulk: true, }, ]; From fdc7bf8ce3e324d907537e869955f97adfe57b41 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Wed, 27 Nov 2024 12:20:42 +0100 Subject: [PATCH 06/10] Default bulk support to true, unless custom Edit is used --- packages/dataviews/src/normalize-fields.ts | 8 ++- .../src/components/post-edit/index.js | 58 +++++++------------ packages/fields/src/fields/author/index.tsx | 1 - packages/fields/src/fields/date/index.tsx | 1 - packages/fields/src/fields/status/index.tsx | 1 - packages/fields/src/fields/title/index.ts | 1 - 6 files changed, 28 insertions(+), 42 deletions(-) diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 0c1e81518c9b4..64c86bd4d0f80 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -64,6 +64,12 @@ export function normalizeFields< Item >( ); }; + let supportsBulk = true; + // If custom Edit component is passed in we default to false for bulk edit support. + if ( typeof field.Edit === 'function' || field.supportsBulk ) { + supportsBulk = field.supportsBulk ?? false; + } + const render = field.render || ( field.elements ? renderFromElements : getValue ); @@ -76,7 +82,7 @@ export function normalizeFields< Item >( sort, isValid, Edit, - supportsBulk: field.supportsBulk ?? false, + supportsBulk, enableHiding: field.enableHiding ?? true, enableSorting: field.enableSorting ?? true, }; diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index 284f7b283fa81..70ba9d230548e 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -22,13 +22,26 @@ import { unlock } from '../../lock-unlock'; const { PostCardPanel, usePostFields } = unlock( editorPrivateApis ); -const fieldsWithBulkEditSupport = [ - 'title', - 'status', - 'date', - 'author', - 'comment_status', -]; +const DATAFORM_CONFIG = { + type: 'panel', + fields: [ + { + id: 'featured_media', + layout: 'regular', + }, + 'title', + { + id: 'status', + label: __( 'Status & Visibility' ), + children: [ 'status', 'password' ], + }, + 'author', + 'date', + 'slug', + 'parent', + 'comment_status', + ], +}; function PostEditForm( { postType, postId } ) { const ids = useMemo( () => postId.split( ',' ), [ postId ] ); @@ -75,35 +88,6 @@ function PostEditForm( { postType, postId } ) { [ _fields ] ); - const form = useMemo( - () => ( { - type: 'panel', - fields: [ - { - id: 'featured_media', - layout: 'regular', - }, - 'title', - { - id: 'status', - label: __( 'Status & Visibility' ), - children: [ 'status', 'password' ], - }, - 'author', - 'date', - 'slug', - 'parent', - 'comment_status', - ].filter( - ( field ) => - ids.length === 1 || - fieldsWithBulkEditSupport.includes( - typeof field === 'string' ? field : field.id - ) - ), - } ), - [ ids ] - ); const onChange = ( edits ) => { for ( const id of ids ) { if ( @@ -133,7 +117,7 @@ function PostEditForm( { postType, postId } ) { diff --git a/packages/fields/src/fields/author/index.tsx b/packages/fields/src/fields/author/index.tsx index 08bfa3ca8d87b..22f9409e05155 100644 --- a/packages/fields/src/fields/author/index.tsx +++ b/packages/fields/src/fields/author/index.tsx @@ -15,7 +15,6 @@ const authorField: Field< BasePostWithEmbeddedAuthor > = { id: 'author', type: 'integer', elements: [], - supportsBulk: true, render: AuthorView, sort: ( a, b, direction ) => { const nameA = a._embedded?.author?.[ 0 ]?.name || ''; diff --git a/packages/fields/src/fields/date/index.tsx b/packages/fields/src/fields/date/index.tsx index c9e1d28a6dba9..49bd0807de669 100644 --- a/packages/fields/src/fields/date/index.tsx +++ b/packages/fields/src/fields/date/index.tsx @@ -15,7 +15,6 @@ const dateField: Field< BasePost > = { type: 'datetime', label: __( 'Date' ), render: DateView, - supportsBulk: true, }; /** diff --git a/packages/fields/src/fields/status/index.tsx b/packages/fields/src/fields/status/index.tsx index 26e3ad22b6e98..374277bc7260e 100644 --- a/packages/fields/src/fields/status/index.tsx +++ b/packages/fields/src/fields/status/index.tsx @@ -20,7 +20,6 @@ const statusField: Field< BasePost > = { elements: STATUSES, render: StatusView, Edit: 'radio', - supportsBulk: true, enableSorting: false, filterBy: { operators: [ OPERATOR_IS_ANY ], diff --git a/packages/fields/src/fields/title/index.ts b/packages/fields/src/fields/title/index.ts index 33414ce172ea9..d8e6f25276d6b 100644 --- a/packages/fields/src/fields/title/index.ts +++ b/packages/fields/src/fields/title/index.ts @@ -19,7 +19,6 @@ const titleField: Field< BasePost > = { getValue: ( { item } ) => getItemTitle( item ), render: TitleView, enableHiding: false, - supportsBulk: true, }; export default titleField; From 4df01658fc8207f357f04c08f75f953002d47f49 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 2 Dec 2024 15:29:39 +0100 Subject: [PATCH 07/10] Update supports bulk setting name --- .../dataviews/src/dataforms-layouts/data-form-layout.tsx | 5 +++-- packages/dataviews/src/normalize-fields.ts | 4 ++-- packages/dataviews/src/types.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx index 2afa7933c8934..55542d434cb7a 100644 --- a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx +++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx @@ -28,7 +28,7 @@ function doesCombinedFieldSupportBulkEdits< Item >( return fieldDefinitions.find( ( fieldDefinition ) => fieldDefinition.id === fieldId - )?.supportsBulk; + )?.supportsBulkEditing; } ); } @@ -97,7 +97,8 @@ export function DataFormLayout< Item extends object >( { formField, fieldDefinitions ) ) || - ( fieldDefinition && ! fieldDefinition.supportsBulk ) ) + ( fieldDefinition && + ! fieldDefinition.supportsBulkEditing ) ) ) { return null; } diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 64c86bd4d0f80..6fe06f6de55f3 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -66,8 +66,8 @@ export function normalizeFields< Item >( let supportsBulk = true; // If custom Edit component is passed in we default to false for bulk edit support. - if ( typeof field.Edit === 'function' || field.supportsBulk ) { - supportsBulk = field.supportsBulk ?? false; + if ( typeof field.Edit === 'function' || field.supportsBulkEditing ) { + supportsBulk = field.supportsBulkEditing ?? false; } const render = diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 71c8806548eaa..b05e13ae5dffc 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -160,9 +160,9 @@ export type Field< Item > = { getValue?: ( args: { item: Item } ) => any; /** - * Whether the action can be used as a bulk action. + * Whether the field supports bulk editing. */ - supportsBulk?: boolean; + supportsBulkEditing?: boolean; }; export type NormalizedField< Item > = Field< Item > & { From ac03ed1d0ad1bb31384afd2e768a078481e036ca Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Mon, 2 Dec 2024 16:06:56 +0100 Subject: [PATCH 08/10] Address renaming missed in the last commit --- packages/dataviews/src/normalize-fields.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 6fe06f6de55f3..c32fcf81e9ae7 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -64,10 +64,10 @@ export function normalizeFields< Item >( ); }; - let supportsBulk = true; + let supportsBulkEditing = true; // If custom Edit component is passed in we default to false for bulk edit support. if ( typeof field.Edit === 'function' || field.supportsBulkEditing ) { - supportsBulk = field.supportsBulkEditing ?? false; + supportsBulkEditing = field.supportsBulkEditing ?? false; } const render = @@ -82,7 +82,7 @@ export function normalizeFields< Item >( sort, isValid, Edit, - supportsBulk, + supportsBulkEditing, enableHiding: field.enableHiding ?? true, enableSorting: field.enableSorting ?? true, }; From c27bd653fd124520594752d6950719c79857fa86 Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 3 Dec 2024 10:11:24 +0100 Subject: [PATCH 09/10] Add mixed symbol value for bulk editing --- packages/dataviews/src/components/dataform/index.tsx | 3 +++ packages/dataviews/src/constants.ts | 3 +++ packages/dataviews/src/dataforms-layouts/panel/index.tsx | 4 ++-- packages/dataviews/src/index.ts | 1 + packages/fields/src/fields/title/index.ts | 9 +++++++-- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index 202afc45cbf26..1771d60aa5c1a 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -10,6 +10,7 @@ import type { DataFormProps } from '../../types'; import { DataFormProvider } from '../dataform-context'; import { normalizeFields } from '../../normalize-fields'; import { DataFormLayout } from '../../dataforms-layouts/data-form-layout'; +import { MIXED_VALUE } from '../../constants'; /** * Loops through the list of data items and returns an object with the intersecting ( same ) key and values. @@ -26,6 +27,8 @@ function getIntersectingValues< Item extends object >( data: Item[] ): Item { } ); if ( intersects ) { intersectingValues[ key ] = firstRecord[ key ]; + } else { + intersectingValues[ key ] = MIXED_VALUE as Item[ keyof Item ]; } } return intersectingValues; diff --git a/packages/dataviews/src/constants.ts b/packages/dataviews/src/constants.ts index 5ae94c7eb4a13..fa5a9cd179de9 100644 --- a/packages/dataviews/src/constants.ts +++ b/packages/dataviews/src/constants.ts @@ -68,3 +68,6 @@ export const sortIcons = { export const LAYOUT_TABLE = 'table'; export const LAYOUT_GRID = 'grid'; export const LAYOUT_LIST = 'list'; + +// Dataform mixed value. +export const MIXED_VALUE = Symbol.for( 'DATAFORM_MIXED_VALUE' ); diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index f3d35edea2805..f7f70d8571c61 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -26,6 +26,7 @@ import type { import DataFormContext from '../../components/dataform-context'; import { DataFormLayout } from '../data-form-layout'; import { isCombinedField } from '../is-combined-field'; +import { MIXED_VALUE } from '../../constants'; function DropdownHeader( { title, @@ -114,8 +115,7 @@ function PanelDropdown< Item extends object >( { ); const fieldValue = fieldDefinition.getValue( { item: data } ); - const showMixedValue = - isBulkEditing && ( fieldValue === undefined || fieldValue === '' ); + const showMixedValue = isBulkEditing && fieldValue === MIXED_VALUE; return ( = { id: 'title', label: __( 'Title' ), placeholder: __( 'No title' ), - getValue: ( { item } ) => getItemTitle( item ), + getValue: ( { item } ) => { + if ( typeof item.title === 'symbol' ) { + return item.title; + } + return getItemTitle( item ); + }, render: TitleView, enableHiding: false, }; From 366883a3af37779be97fd4454c94a2615f987c9a Mon Sep 17 00:00:00 2001 From: Lourens Schep Date: Tue, 3 Dec 2024 12:37:09 +0100 Subject: [PATCH 10/10] Skip underscore keys and support objects in bulk editing values --- .../src/components/dataform/index.tsx | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index 1771d60aa5c1a..b96df5f186c75 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -14,6 +14,7 @@ import { MIXED_VALUE } from '../../constants'; /** * Loops through the list of data items and returns an object with the intersecting ( same ) key and values. + * Skips keys that start with an underscore. * * @param data list of items. */ @@ -21,14 +22,26 @@ function getIntersectingValues< Item extends object >( data: Item[] ): Item { const intersectingValues = {} as Item; const keys = Object.keys( data[ 0 ] ) as Array< keyof Item >; for ( const key of keys ) { + // Skip keys that start with underscore. + if ( key.toString().startsWith( '_' ) ) { + continue; + } const [ firstRecord, ...remainingRecords ] = data; - const intersects = remainingRecords.every( ( item ) => { - return item[ key ] === firstRecord[ key ]; - } ); - if ( intersects ) { - intersectingValues[ key ] = firstRecord[ key ]; + + if ( typeof firstRecord[ key ] === 'object' ) { + // Traverse through nested objects. + intersectingValues[ key ] = getIntersectingValues( + data.map( ( item ) => item[ key ] as object ) + ) as Item[ keyof Item ]; } else { - intersectingValues[ key ] = MIXED_VALUE as Item[ keyof Item ]; + const intersects = remainingRecords.every( ( item ) => { + return item[ key ] === firstRecord[ key ]; + } ); + if ( intersects ) { + intersectingValues[ key ] = firstRecord[ key ]; + } else { + intersectingValues[ key ] = MIXED_VALUE as Item[ keyof Item ]; + } } } return intersectingValues;