( {
justify="space-between"
className="dataviews-view-grid__title-actions"
>
-
- { renderedPrimaryField }
+
+
+ { renderedPrimaryField }
+
@@ -170,6 +190,8 @@ export default function ViewGrid< Item >( {
getItemId,
isLoading,
onChangeSelection,
+ onClickItem,
+ isItemClickable,
selection,
view,
density,
@@ -223,6 +245,8 @@ export default function ViewGrid< Item >( {
key={ getItemId( item ) }
selection={ selection }
onChangeSelection={ onChangeSelection }
+ onClickItem={ onClickItem }
+ isItemClickable={ isItemClickable }
getItemId={ getItemId }
item={ item }
actions={ actions }
diff --git a/packages/dataviews/src/dataviews-layouts/grid/style.scss b/packages/dataviews/src/dataviews-layouts/grid/style.scss
index 6286ed94685a04..55768240a18714 100644
--- a/packages/dataviews/src/dataviews-layouts/grid/style.scss
+++ b/packages/dataviews/src/dataviews-layouts/grid/style.scss
@@ -17,8 +17,13 @@
.dataviews-view-grid__primary-field {
min-height: $grid-unit-40; // Preserve layout when there is no ellipsis button
+
+ &--clickable {
+ width: fit-content;
+ }
}
+
&.is-selected {
.dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-value {
color: $gray-900;
diff --git a/packages/dataviews/src/dataviews-layouts/table/index.tsx b/packages/dataviews/src/dataviews-layouts/table/index.tsx
index 4e1607b01489c2..8ef41db1c38798 100644
--- a/packages/dataviews/src/dataviews-layouts/table/index.tsx
+++ b/packages/dataviews/src/dataviews-layouts/table/index.tsx
@@ -35,11 +35,14 @@ import type {
import type { SetSelection } from '../../private-types';
import ColumnHeaderMenu from './column-header-menu';
import { getVisibleFieldIds } from '../index';
+import getClickableItemProps from '../utils/get-clickable-item-props';
interface TableColumnFieldProps< Item > {
primaryField?: NormalizedField< Item >;
field: NormalizedField< Item >;
item: Item;
+ isItemClickable: ( item: Item ) => boolean;
+ onClickItem: ( item: Item ) => void;
}
interface TableColumnCombinedProps< Item > {
@@ -48,6 +51,8 @@ interface TableColumnCombinedProps< Item > {
field: CombinedField;
item: Item;
view: ViewTableType;
+ isItemClickable: ( item: Item ) => boolean;
+ onClickItem: ( item: Item ) => void;
}
interface TableColumnProps< Item > {
@@ -56,6 +61,8 @@ interface TableColumnProps< Item > {
item: Item;
column: string;
view: ViewTableType;
+ isItemClickable: ( item: Item ) => boolean;
+ onClickItem: ( item: Item ) => void;
}
interface TableRowProps< Item > {
@@ -69,6 +76,8 @@ interface TableRowProps< Item > {
selection: string[];
getItemId: ( item: Item ) => string;
onChangeSelection: SetSelection;
+ isItemClickable: ( item: Item ) => boolean;
+ onClickItem: ( item: Item ) => void;
}
function TableColumn< Item >( {
@@ -102,15 +111,29 @@ function TableColumnField< Item >( {
primaryField,
item,
field,
+ isItemClickable,
+ onClickItem,
}: TableColumnFieldProps< Item > ) {
+ const isPrimaryField = primaryField?.id === field.id;
+ const isItemClickableField = ( i: Item ) =>
+ isItemClickable( i ) && isPrimaryField;
+
+ const clickableProps = getClickableItemProps(
+ item,
+ isItemClickableField,
+ onClickItem,
+ 'dataviews-view-table__cell-content'
+ );
+
return (
);
}
@@ -139,6 +162,8 @@ function TableRow< Item >( {
primaryField,
selection,
getItemId,
+ isItemClickable,
+ onClickItem,
onChangeSelection,
}: TableRowProps< Item > ) {
const hasPossibleBulkAction = useHasAPossibleBulkAction( actions, item );
@@ -214,6 +239,8 @@ function TableRow< Item >( {
( {
onChangeSelection,
selection,
setOpenedFilter,
+ onClickItem,
+ isItemClickable,
view,
}: ViewTableProps< Item > ) {
const headerMenuRefs = useRef<
@@ -392,6 +421,8 @@ function ViewTable< Item >( {
selection={ selection }
getItemId={ getItemId }
onChangeSelection={ onChangeSelection }
+ onClickItem={ onClickItem }
+ isItemClickable={ isItemClickable }
/>
) ) }
diff --git a/packages/dataviews/src/dataviews-layouts/utils/get-clickable-item-props.ts b/packages/dataviews/src/dataviews-layouts/utils/get-clickable-item-props.ts
new file mode 100644
index 00000000000000..e2a6081a68fa3e
--- /dev/null
+++ b/packages/dataviews/src/dataviews-layouts/utils/get-clickable-item-props.ts
@@ -0,0 +1,22 @@
+export default function getClickableItemProps< Item >(
+ item: Item,
+ isItemClickable: ( item: Item ) => boolean,
+ onClickItem: ( item: Item ) => void,
+ className: string
+) {
+ if ( ! isItemClickable( item ) ) {
+ return { className };
+ }
+
+ return {
+ className: `${ className } ${ className }--clickable`,
+ role: 'button',
+ tabIndex: 0,
+ onClick: () => onClickItem( item ),
+ onKeyDown: ( event: React.KeyboardEvent ) => {
+ if ( event.key === 'Enter' || event.key === '' ) {
+ onClickItem( item );
+ }
+ },
+ };
+}
diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts
index 5ef219e45a4787..562f29fcce84fe 100644
--- a/packages/dataviews/src/normalize-fields.ts
+++ b/packages/dataviews/src/normalize-fields.ts
@@ -11,6 +11,22 @@ import type {
import { getControl } from './dataform-controls';
import DataFormCombinedEdit from './components/dataform-combined-edit';
+const getValueFromId =
+ ( id: string ) =>
+ ( { item }: { item: any } ) => {
+ const path = id.split( '.' );
+ let value = item;
+ for ( const segment of path ) {
+ if ( value.hasOwnProperty( segment ) ) {
+ value = value[ segment ];
+ } else {
+ value = undefined;
+ }
+ }
+
+ return value;
+ };
+
/**
* Apply default values and normalize the fields config.
*
@@ -23,8 +39,7 @@ export function normalizeFields< Item >(
return fields.map( ( field ) => {
const fieldTypeDefinition = getFieldTypeDefinition( field.type );
- const getValue =
- field.getValue || ( ( { item } ) => ( item as any )[ field.id ] );
+ const getValue = field.getValue || getValueFromId( field.id );
const sort =
field.sort ??
diff --git a/packages/dataviews/src/test/normalize-fields.ts b/packages/dataviews/src/test/normalize-fields.ts
new file mode 100644
index 00000000000000..dce6a19766dcaa
--- /dev/null
+++ b/packages/dataviews/src/test/normalize-fields.ts
@@ -0,0 +1,45 @@
+/**
+ * Internal dependencies
+ */
+import { normalizeFields } from '../normalize-fields';
+import type { Field } from '../types';
+
+describe( 'normalizeFields: default getValue', () => {
+ describe( 'getValue from ID', () => {
+ it( 'user', () => {
+ const item = { user: 'value' };
+ const fields: Field< {} >[] = [
+ {
+ id: 'user',
+ },
+ ];
+ const normalizedFields = normalizeFields( fields );
+ const result = normalizedFields[ 0 ].getValue( { item } );
+ expect( result ).toBe( 'value' );
+ } );
+
+ it( 'user.name', () => {
+ const item = { user: { name: 'value' } };
+ const fields: Field< {} >[] = [
+ {
+ id: 'user.name',
+ },
+ ];
+ const normalizedFields = normalizeFields( fields );
+ const result = normalizedFields[ 0 ].getValue( { item } );
+ expect( result ).toBe( 'value' );
+ } );
+
+ it( 'user.name.first', () => {
+ const item = { user: { name: { first: 'value' } } };
+ const fields: Field< {} >[] = [
+ {
+ id: 'user.name.first',
+ },
+ ];
+ const normalizedFields = normalizeFields( fields );
+ const result = normalizedFields[ 0 ].getValue( { item } );
+ expect( result ).toBe( 'value' );
+ } );
+ } );
+} );
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 0ea0965704d18c..71990f72d4eecd 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -498,6 +498,8 @@ export interface ViewBaseProps< Item > {
onChangeSelection: SetSelection;
selection: string[];
setOpenedFilter: ( fieldId: string ) => void;
+ onClickItem: ( item: Item ) => void;
+ isItemClickable: ( item: Item ) => boolean;
view: View;
density: number;
}
diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js
index 8f0ca0c5b29718..94b56a197e1bc1 100644
--- a/packages/edit-site/src/components/editor/index.js
+++ b/packages/edit-site/src/components/editor/index.js
@@ -218,7 +218,13 @@ export default function EditSiteEditor( { isPostsList = false } ) {
{ isEditMode && }
{ ! isReady ? : null }
- { isEditMode && }
+ { isEditMode && (
+
+ ) }
{ isReady && (
dateI18n(
getSettings().formats.datetimeAbbreviated,
getDate( dateToDisplay )
);
-function PostStatusField( { item } ) {
- const status = STATUSES.find( ( { value } ) => value === item.status );
- const label = status?.label || item.status;
- const icon = status?.icon;
- return (
-
- { icon && (
-
-
-
- ) }
- { label }
-
- );
-}
-
function PostAuthorField( { item } ) {
const { text, imageUrl } = useSelect(
( select ) => {
@@ -139,7 +71,7 @@ function PostAuthorField( { item } ) {
);
}
-function usePostFields( viewType ) {
+function usePostFields() {
const { records: authors, isResolving: isLoadingAuthors } =
useEntityRecords( 'root', 'user', { per_page: -1 } );
@@ -164,30 +96,10 @@ function usePostFields( viewType ) {
? item.title
: item.title?.raw,
render: ( { item } ) => {
- const addLink =
- [ LAYOUT_TABLE, LAYOUT_GRID ].includes( viewType ) &&
- item.status !== 'trash';
const renderedTitle =
typeof item.title === 'string'
? item.title
: item.title?.rendered;
- const title = addLink ? (
-
- { decodeEntities( renderedTitle ) ||
- __( '(no title)' ) }
-
- ) : (
-
- { decodeEntities( renderedTitle ) ||
- __( '(no title)' ) }
-
- );
let suffix = '';
if ( item.id === frontPageId ) {
@@ -210,7 +122,10 @@ function usePostFields( viewType ) {
alignment="center"
justify="flex-start"
>
- { title }
+
+ { decodeEntities( renderedTitle ) ||
+ __( '(no title)' ) }
+
{ suffix }
);
@@ -236,18 +151,7 @@ function usePostFields( viewType ) {
: nameB.localeCompare( nameA );
},
},
- {
- label: __( 'Status' ),
- id: 'status',
- type: 'text',
- elements: STATUSES,
- render: PostStatusField,
- Edit: 'radio',
- enableSorting: false,
- filterBy: {
- operators: [ OPERATOR_IS_ANY ],
- },
- },
+ statusField,
{
label: __( 'Date' ),
id: 'date',
@@ -327,35 +231,10 @@ function usePostFields( viewType ) {
},
slugField,
parentField,
- {
- id: 'comment_status',
- label: __( 'Discussion' ),
- type: 'text',
- Edit: 'radio',
- enableSorting: false,
- filterBy: {
- operators: [],
- },
- elements: [
- {
- value: 'open',
- label: __( 'Open' ),
- description: __(
- 'Visitors can add new comments and replies.'
- ),
- },
- {
- value: 'closed',
- label: __( 'Closed' ),
- description: __(
- 'Visitors cannot add new comments or replies. Existing comments remain visible.'
- ),
- },
- ],
- },
+ commentStatusField,
passwordField,
],
- [ authors, viewType, frontPageId, postsPageId ]
+ [ authors, frontPageId, postsPageId ]
);
return {
diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js
index 4985af3050bd8d..4639cb3c950b76 100644
--- a/packages/edit-site/src/components/post-list/index.js
+++ b/packages/edit-site/src/components/post-list/index.js
@@ -208,9 +208,7 @@ export default function PostList( { postType } ) {
return found?.filters ?? [];
};
- const { isLoading: isLoadingFields, fields: _fields } = usePostFields(
- view.type
- );
+ const { isLoading: isLoadingFields, fields: _fields } = usePostFields();
const fields = useMemo( () => {
const activeViewFilters = getActiveViewFilters(
defaultViews,
@@ -402,6 +400,14 @@ export default function PostList( { postType } ) {
onChangeView={ setView }
selection={ selection }
onChangeSelection={ onChangeSelection }
+ isItemClickable={ ( item ) => item.status !== 'trash' }
+ onClickItem={ ( { id } ) => {
+ history.push( {
+ postId: id,
+ postType,
+ canvas: 'edit',
+ } );
+ } }
getItemId={ getItemId }
defaultLayouts={ defaultLayouts }
header={
diff --git a/packages/edit-site/src/components/post-list/style.scss b/packages/edit-site/src/components/post-list/style.scss
index db6a32408c7921..14bb11b41d4450 100644
--- a/packages/edit-site/src/components/post-list/style.scss
+++ b/packages/edit-site/src/components/post-list/style.scss
@@ -9,7 +9,9 @@
width: 100%;
border-radius: $grid-unit-05;
- &.is-layout-table:not(:has(.edit-site-post-list__featured-image-button)),
+ &.is-layout-table:not(
+ :has(.edit-site-post-list__featured-image-button)
+),
&.is-layout-table .edit-site-post-list__featured-image-button {
width: $grid-unit-40;
height: $grid-unit-40;
@@ -46,7 +48,9 @@
border-radius: $grid-unit-05;
&:focus-visible {
- box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
+ box-shadow:
+ 0 0 0 var(--wp-admin-border-width-focus)
+ var(--wp-admin-theme-color);
// Windows High Contrast mode will show this outline, but not the box-shadow.
outline: 2px solid transparent;
}
@@ -54,7 +58,9 @@
.dataviews-view-grid__card.is-selected {
.edit-site-post-list__featured-image-button::after {
- box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
+ box-shadow:
+ inset 0 0 0 var(--wp-admin-border-width-focus)
+ var(--wp-admin-theme-color);
background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
}
}
@@ -64,6 +70,26 @@
overflow: hidden;
}
+.dataviews-view-grid__primary-field.dataviews-view-grid__primary-field--clickable
+.edit-site-post-list__title
+span,
+.dataviews-view-table__primary-field > .dataviews-view-table__cell-content--clickable
+.edit-site-post-list__title
+span {
+ text-decoration: none;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ display: block;
+ flex-grow: 0;
+ color: $gray-900;
+
+ &:hover {
+ color: var(--wp-admin-theme-color);
+ }
+ @include link-reset();
+}
+
.edit-site-post-list__title-badge {
background: $gray-100;
color: $gray-800;
diff --git a/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js
index afa9f489dde22b..342fb1b5db52d2 100644
--- a/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js
+++ b/packages/edit-site/src/components/sidebar-global-styles-wrapper/index.js
@@ -132,6 +132,11 @@ export default function GlobalStylesUIWrapper() {
onPathChange( '/colors/palette' );
return;
}
+ if ( blockName === 'typography' ) {
+ // Go to typography Global Styles.
+ onPathChange( '/typography' );
+ return;
+ }
// Now go to the selected block.
onPathChange(
diff --git a/packages/edit-site/src/components/style-book/constants.ts b/packages/edit-site/src/components/style-book/constants.ts
index 6aa280c937d42d..7b13e3d4ef7f60 100644
--- a/packages/edit-site/src/components/style-book/constants.ts
+++ b/packages/edit-site/src/components/style-book/constants.ts
@@ -107,6 +107,11 @@ export const STYLE_BOOK_THEME_SUBCATEGORIES: Omit<
];
export const STYLE_BOOK_CATEGORIES: StyleBookCategory[] = [
+ {
+ slug: 'overview',
+ title: __( 'Overview' ),
+ blocks: [],
+ },
{
slug: 'text',
title: __( 'Text' ),
@@ -249,16 +254,19 @@ export const STYLE_BOOK_IFRAME_STYLES = `
.edit-site-style-book__example-preview {
width: 100%;
}
+
+ .is-wide .edit-site-style-book__example-preview {
+ width: calc(100% - 120px);
+ }
.edit-site-style-book__example-preview .block-editor-block-list__insertion-point,
.edit-site-style-book__example-preview .block-list-appender {
display: none;
}
-
- .edit-site-style-book__example-preview .is-root-container > .wp-block:first-child {
+ :where(.is-root-container > .wp-block:first-child) {
margin-top: 0;
}
- .edit-site-style-book__example-preview .is-root-container > .wp-block:last-child {
+ :where(.is-root-container > .wp-block:last-child) {
margin-bottom: 0;
}
`;
diff --git a/packages/edit-site/src/components/style-book/examples.tsx b/packages/edit-site/src/components/style-book/examples.tsx
index 9f4badd99a6582..2fefe227ca52b7 100644
--- a/packages/edit-site/src/components/style-book/examples.tsx
+++ b/packages/edit-site/src/components/style-book/examples.tsx
@@ -62,6 +62,103 @@ function getColorExamples( colors: MultiOriginPalettes ): BlockExample[] {
return examples;
}
+/**
+ * Returns examples for the overview page.
+ *
+ * @param {MultiOriginPalettes} colors Global Styles color palettes per origin.
+ * @return {BlockExample[]} An array of block examples.
+ */
+function getOverviewBlockExamples(
+ colors: MultiOriginPalettes
+): BlockExample[] {
+ const examples: BlockExample[] = [];
+
+ // Get theme palette from colors.
+ const themePalette = colors.colors.find(
+ ( origin: ColorOrigin ) => origin.slug === 'theme'
+ );
+
+ if ( themePalette ) {
+ const themeColorexample: BlockExample = {
+ name: 'theme-colors',
+ title: __( 'Colors' ),
+ category: 'overview',
+ content: (
+
+ ),
+ };
+
+ examples.push( themeColorexample );
+ }
+
+ const headingBlock = createBlock( 'core/heading', {
+ content: __(
+ `AaBbCcDdEeFfGgHhiiJjKkLIMmNnOoPpQakRrssTtUuVVWwXxxYyZzOl23356789X{(…)},2!*&:/A@HELFO™`
+ ),
+ level: 1,
+ } );
+ const firstParagraphBlock = createBlock( 'core/paragraph', {
+ content: __(
+ `A paragraph in a website refers to a distinct block of text that is used to present and organize information. It is a fundamental unit of content in web design and is typically composed of a group of related sentences or thoughts focused on a particular topic or idea. Paragraphs play a crucial role in improving the readability and user experience of a website. They break down the text into smaller, manageable chunks, allowing readers to scan the content more easily.`
+ ),
+ } );
+ const secondParagraphBlock = createBlock( 'core/paragraph', {
+ content: __(
+ `Additionally, paragraphs help structure the flow of information and provide logical breaks between different concepts or pieces of information. In terms of formatting, paragraphs in websites are commonly denoted by a vertical gap or indentation between each block of text. This visual separation helps visually distinguish one paragraph from another, creating a clear and organized layout that guides the reader through the content smoothly.`
+ ),
+ } );
+
+ const textExample = {
+ name: 'typography',
+ title: __( 'Typography' ),
+ category: 'overview',
+ blocks: [
+ headingBlock,
+ createBlock(
+ 'core/group',
+ {
+ layout: {
+ type: 'grid',
+ columnCount: 2,
+ minimumColumnWidth: '12rem',
+ },
+ style: {
+ spacing: {
+ blockGap: '1.5rem',
+ },
+ },
+ },
+ [ firstParagraphBlock, secondParagraphBlock ]
+ ),
+ ],
+ };
+ examples.push( textExample );
+
+ const otherBlockExamples = [
+ 'core/image',
+ 'core/separator',
+ 'core/buttons',
+ 'core/pullquote',
+ 'core/search',
+ ];
+
+ // Get examples for other blocks and put them in order of above array.
+ otherBlockExamples.forEach( ( blockName ) => {
+ const blockType = getBlockType( blockName );
+ if ( blockType && blockType.example ) {
+ const blockExample: BlockExample = {
+ name: blockName,
+ title: blockType.title,
+ category: 'overview',
+ blocks: getBlockFromExample( blockName, blockType.example ),
+ };
+ examples.push( blockExample );
+ }
+ } );
+
+ return examples;
+}
+
/**
* Returns a list of examples for registered block types.
*
@@ -109,5 +206,12 @@ export function getExamples( colors: MultiOriginPalettes ): BlockExample[] {
};
const colorExamples = getColorExamples( colors );
- return [ headingsExample, ...colorExamples, ...nonHeadingBlockExamples ];
+ const overviewBlockExamples = getOverviewBlockExamples( colors );
+
+ return [
+ headingsExample,
+ ...colorExamples,
+ ...nonHeadingBlockExamples,
+ ...overviewBlockExamples,
+ ];
}
diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js
index 9918c169ff6ab0..de4c38bd40c05d 100644
--- a/packages/edit-site/src/components/style-book/index.js
+++ b/packages/edit-site/src/components/style-book/index.js
@@ -203,6 +203,22 @@ function StyleBook( {
[ examples ]
);
+ const examplesForSinglePageUse = [];
+ const overviewCategoryExamples = getExamplesByCategory(
+ { slug: 'overview' },
+ examples
+ );
+ examplesForSinglePageUse.push( ...overviewCategoryExamples.examples );
+ const otherExamples = examples.filter( ( example ) => {
+ return (
+ example.category !== 'overview' &&
+ ! overviewCategoryExamples.examples.find(
+ ( overviewExample ) => overviewExample.name === example.name
+ )
+ );
+ } );
+ examplesForSinglePageUse.push( ...otherExamples );
+
const { base: baseConfig } = useContext( GlobalStylesContext );
const goTo = getStyleBookNavigationFromPath( path );
@@ -286,7 +302,7 @@ function StyleBook( {
) : (
-
-
+ { postType === 'page' && }
+ { postType === 'wp_template' && }
>
);
}
diff --git a/packages/edit-site/src/components/welcome-guide/page.js b/packages/edit-site/src/components/welcome-guide/page.js
index db89d9b653ad58..41bb80342280cf 100644
--- a/packages/edit-site/src/components/welcome-guide/page.js
+++ b/packages/edit-site/src/components/welcome-guide/page.js
@@ -6,11 +6,6 @@ import { Guide } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { store as preferencesStore } from '@wordpress/preferences';
-/**
- * Internal dependencies
- */
-import { store as editSiteStore } from '../../store';
-
export default function WelcomeGuidePage() {
const { toggle } = useDispatch( preferencesStore );
@@ -23,8 +18,7 @@ export default function WelcomeGuidePage() {
'core/edit-site',
'welcomeGuide'
);
- const { isPage } = select( editSiteStore );
- return isPageActive && ! isEditorActive && isPage();
+ return isPageActive && ! isEditorActive;
}, [] );
if ( ! isVisible ) {
diff --git a/packages/edit-site/src/components/welcome-guide/template.js b/packages/edit-site/src/components/welcome-guide/template.js
index e6568a23bb3a3c..bf08bc7fa668bd 100644
--- a/packages/edit-site/src/components/welcome-guide/template.js
+++ b/packages/edit-site/src/components/welcome-guide/template.js
@@ -7,16 +7,9 @@ import { __ } from '@wordpress/i18n';
import { store as preferencesStore } from '@wordpress/preferences';
import { store as editorStore } from '@wordpress/editor';
-/**
- * Internal dependencies
- */
-import useEditedEntityRecord from '../use-edited-entity-record';
-
export default function WelcomeGuideTemplate() {
const { toggle } = useDispatch( preferencesStore );
- const { isLoaded, record } = useEditedEntityRecord();
- const isPostTypeTemplate = isLoaded && record.type === 'wp_template';
const { isActive, hasPreviousEntity } = useSelect( ( select ) => {
const { getEditorSettings } = select( editorStore );
const { get } = select( preferencesStore );
@@ -26,7 +19,7 @@ export default function WelcomeGuideTemplate() {
!! getEditorSettings().onNavigateToPreviousEntityRecord,
};
}, [] );
- const isVisible = isActive && isPostTypeTemplate && hasPreviousEntity;
+ const isVisible = isActive && hasPreviousEntity;
if ( ! isVisible ) {
return null;
diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss
index 03ec43648f120f..0e5744fe362e35 100644
--- a/packages/edit-site/src/style.scss
+++ b/packages/edit-site/src/style.scss
@@ -1,7 +1,5 @@
@import "../../dataviews/src/style.scss";
-@import "../../fields/src/styles.scss";
-@import "../../fields/src/fields/featured-image/style.scss";
-
+@import "../../fields/src/style.scss";
@import "./components/add-new-template/style.scss";
@import "./components/block-editor/style.scss";
@import "./components/canvas-loader/style.scss";
diff --git a/packages/fields/README.md b/packages/fields/README.md
index 214f3d6ee3a50a..1d673c4d46c7b1 100644
--- a/packages/fields/README.md
+++ b/packages/fields/README.md
@@ -14,6 +14,10 @@ npm install @wordpress/fields --save
+### commentStatusField
+
+Comment status field for BasePost.
+
### deletePost
Undocumented declaration.
@@ -82,6 +86,10 @@ Undocumented declaration.
Undocumented declaration.
+### statusField
+
+Status field for BasePost.
+
### titleField
Undocumented declaration.
diff --git a/packages/fields/src/fields/comment-status/index.tsx b/packages/fields/src/fields/comment-status/index.tsx
new file mode 100644
index 00000000000000..7f373bc14e2108
--- /dev/null
+++ b/packages/fields/src/fields/comment-status/index.tsx
@@ -0,0 +1,40 @@
+/**
+ * WordPress dependencies
+ */
+import type { Field } from '@wordpress/dataviews';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import type { BasePost } from '../../types';
+
+const commentStatusField: Field< BasePost > = {
+ id: 'comment_status',
+ label: __( 'Discussion' ),
+ type: 'text',
+ Edit: 'radio',
+ enableSorting: false,
+ filterBy: {
+ operators: [],
+ },
+ elements: [
+ {
+ value: 'open',
+ label: __( 'Open' ),
+ description: __( 'Visitors can add new comments and replies.' ),
+ },
+ {
+ value: 'closed',
+ label: __( 'Closed' ),
+ description: __(
+ 'Visitors cannot add new comments or replies. Existing comments remain visible.'
+ ),
+ },
+ ],
+};
+
+/**
+ * Comment status field for BasePost.
+ */
+export default commentStatusField;
diff --git a/packages/fields/src/fields/featured-image/featured-image-edit.tsx b/packages/fields/src/fields/featured-image/featured-image-edit.tsx
index b0dc612cdcfa50..ee51e5c60f13e0 100644
--- a/packages/fields/src/fields/featured-image/featured-image-edit.tsx
+++ b/packages/fields/src/fields/featured-image/featured-image-edit.tsx
@@ -9,11 +9,12 @@ import { MediaUpload } from '@wordpress/media-utils';
import { lineSolid } from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';
import type { DataFormControlProps } from '@wordpress/dataviews';
+import { __ } from '@wordpress/i18n';
+
/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
-import { __ } from '@wordpress/i18n';
export const FeaturedImageEdit = ( {
data,
diff --git a/packages/fields/src/fields/featured-image/featured-image-view.tsx b/packages/fields/src/fields/featured-image/featured-image-view.tsx
index 36793e6f2ff9ab..1f4b55183f2383 100644
--- a/packages/fields/src/fields/featured-image/featured-image-view.tsx
+++ b/packages/fields/src/fields/featured-image/featured-image-view.tsx
@@ -3,12 +3,12 @@
*/
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
+import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
-import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
export const FeaturedImageView = ( {
item,
diff --git a/packages/fields/src/fields/featured-image/index.ts b/packages/fields/src/fields/featured-image/index.ts
index 44f9a1b4064648..62d7e8240aded0 100644
--- a/packages/fields/src/fields/featured-image/index.ts
+++ b/packages/fields/src/fields/featured-image/index.ts
@@ -2,12 +2,12 @@
* WordPress dependencies
*/
import type { Field } from '@wordpress/dataviews';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
-import { __ } from '@wordpress/i18n';
import { FeaturedImageEdit } from './featured-image-edit';
import { FeaturedImageView } from './featured-image-view';
@@ -15,7 +15,6 @@ const featuredImageField: Field< BasePost > = {
id: 'featured_media',
type: 'text',
label: __( 'Featured Image' ),
- getValue: ( { item } ) => item.featured_media,
Edit: FeaturedImageEdit,
render: FeaturedImageView,
enableSorting: false,
diff --git a/packages/fields/src/fields/index.ts b/packages/fields/src/fields/index.ts
index 29cbdeb2a4ba6b..fba34eb2388a39 100644
--- a/packages/fields/src/fields/index.ts
+++ b/packages/fields/src/fields/index.ts
@@ -4,3 +4,5 @@ export { default as orderField } from './order';
export { default as featuredImageField } from './featured-image';
export { default as parentField } from './parent';
export { default as passwordField } from './password';
+export { default as statusField } from './status';
+export { default as commentStatusField } from './comment-status';
diff --git a/packages/fields/src/fields/order/index.ts b/packages/fields/src/fields/order/index.ts
index 2fc0a216dcfa03..984a94c6427fc6 100644
--- a/packages/fields/src/fields/order/index.ts
+++ b/packages/fields/src/fields/order/index.ts
@@ -3,14 +3,15 @@
*/
import type { Field } from '@wordpress/dataviews';
import { __ } from '@wordpress/i18n';
+
/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
const orderField: Field< BasePost > = {
- type: 'integer',
id: 'menu_order',
+ type: 'integer',
label: __( 'Order' ),
description: __( 'Determines the order of pages.' ),
};
diff --git a/packages/fields/src/fields/parent/index.ts b/packages/fields/src/fields/parent/index.ts
index 2476d071b81652..8b833e1d9369df 100644
--- a/packages/fields/src/fields/parent/index.ts
+++ b/packages/fields/src/fields/parent/index.ts
@@ -2,12 +2,12 @@
* WordPress dependencies
*/
import type { Field } from '@wordpress/dataviews';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
-import { __ } from '@wordpress/i18n';
import { ParentEdit } from './parent-edit';
import { ParentView } from './parent-view';
@@ -15,7 +15,6 @@ const parentField: Field< BasePost > = {
id: 'parent',
type: 'text',
label: __( 'Parent' ),
- getValue: ( { item } ) => item.parent,
Edit: ParentEdit,
render: ParentView,
enableSorting: true,
diff --git a/packages/fields/src/fields/parent/parent-edit.tsx b/packages/fields/src/fields/parent/parent-edit.tsx
index 030287b8f8fc55..21cdbee7a365a4 100644
--- a/packages/fields/src/fields/parent/parent-edit.tsx
+++ b/packages/fields/src/fields/parent/parent-edit.tsx
@@ -1,3 +1,8 @@
+/**
+ * External dependencies
+ */
+import removeAccents from 'remove-accents';
+
/**
* WordPress dependencies
*/
@@ -12,21 +17,16 @@ import {
// @ts-ignore
import { store as coreStore } from '@wordpress/core-data';
import type { DataFormControlProps } from '@wordpress/dataviews';
-
-/**
- * External dependencies
- */
-import removeAccents from 'remove-accents';
+import { debounce } from '@wordpress/compose';
+import { decodeEntities } from '@wordpress/html-entities';
+import { __, sprintf } from '@wordpress/i18n';
+import { filterURLForDisplay } from '@wordpress/url';
/**
* Internal dependencies
*/
-import { debounce } from '@wordpress/compose';
-import { decodeEntities } from '@wordpress/html-entities';
-import { __, sprintf } from '@wordpress/i18n';
import type { BasePost } from '../../types';
import { getTitleWithFallbackName } from './utils';
-import { filterURLForDisplay } from '@wordpress/url';
type TreeBase = {
id: number;
diff --git a/packages/fields/src/fields/parent/parent-view.tsx b/packages/fields/src/fields/parent/parent-view.tsx
index f0d449db726c32..20c6cb939b4b92 100644
--- a/packages/fields/src/fields/parent/parent-view.tsx
+++ b/packages/fields/src/fields/parent/parent-view.tsx
@@ -3,14 +3,14 @@
*/
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
+import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
-import type { DataViewRenderFieldProps } from '@wordpress/dataviews';
import { getTitleWithFallbackName } from './utils';
-import { __ } from '@wordpress/i18n';
export const ParentView = ( {
item,
diff --git a/packages/fields/src/fields/password/index.tsx b/packages/fields/src/fields/password/index.tsx
index aa7bc57e3f7cae..dacd0d7435998a 100644
--- a/packages/fields/src/fields/password/index.tsx
+++ b/packages/fields/src/fields/password/index.tsx
@@ -12,7 +12,6 @@ import PasswordEdit from './edit';
const passwordField: Field< BasePost > = {
id: 'password',
type: 'text',
- getValue: ( { item } ) => item.password,
Edit: PasswordEdit,
enableSorting: false,
enableHiding: false,
diff --git a/packages/fields/src/fields/slug/index.ts b/packages/fields/src/fields/slug/index.ts
index 4e81996ceaa6e8..c43fcc679622ac 100644
--- a/packages/fields/src/fields/slug/index.ts
+++ b/packages/fields/src/fields/slug/index.ts
@@ -2,12 +2,12 @@
* WordPress dependencies
*/
import type { Field } from '@wordpress/dataviews';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type { BasePost } from '../../types';
-import { __ } from '@wordpress/i18n';
import SlugEdit from './slug-edit';
import SlugView from './slug-view';
@@ -15,7 +15,6 @@ const slugField: Field< BasePost > = {
id: 'slug',
type: 'text',
label: __( 'Slug' ),
- getValue: ( { item } ) => item.slug,
Edit: SlugEdit,
render: SlugView,
};
diff --git a/packages/fields/src/fields/status/index.tsx b/packages/fields/src/fields/status/index.tsx
new file mode 100644
index 00000000000000..374277bc7260ed
--- /dev/null
+++ b/packages/fields/src/fields/status/index.tsx
@@ -0,0 +1,32 @@
+/**
+ * WordPress dependencies
+ */
+import type { Field } from '@wordpress/dataviews';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import type { BasePost } from '../../types';
+import StatusView from './status-view';
+import STATUSES from './status-elements';
+
+const OPERATOR_IS_ANY = 'isAny';
+
+const statusField: Field< BasePost > = {
+ label: __( 'Status' ),
+ id: 'status',
+ type: 'text',
+ elements: STATUSES,
+ render: StatusView,
+ Edit: 'radio',
+ enableSorting: false,
+ filterBy: {
+ operators: [ OPERATOR_IS_ANY ],
+ },
+};
+
+/**
+ * Status field for BasePost.
+ */
+export default statusField;
diff --git a/packages/fields/src/fields/status/status-elements.tsx b/packages/fields/src/fields/status/status-elements.tsx
new file mode 100644
index 00000000000000..079612270e6379
--- /dev/null
+++ b/packages/fields/src/fields/status/status-elements.tsx
@@ -0,0 +1,50 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ trash,
+ drafts,
+ published,
+ scheduled,
+ pending,
+ notAllowed,
+} from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+
+// See https://github.com/WordPress/gutenberg/issues/55886
+// We do not support custom statutes at the moment.
+const STATUSES = [
+ {
+ value: 'draft',
+ label: __( 'Draft' ),
+ icon: drafts,
+ description: __( 'Not ready to publish.' ),
+ },
+ {
+ value: 'future',
+ label: __( 'Scheduled' ),
+ icon: scheduled,
+ description: __( 'Publish automatically on a chosen date.' ),
+ },
+ {
+ value: 'pending',
+ label: __( 'Pending Review' ),
+ icon: pending,
+ description: __( 'Waiting for review before publishing.' ),
+ },
+ {
+ value: 'private',
+ label: __( 'Private' ),
+ icon: notAllowed,
+ description: __( 'Only visible to site admins and editors.' ),
+ },
+ {
+ value: 'publish',
+ label: __( 'Published' ),
+ icon: published,
+ description: __( 'Visible to everyone.' ),
+ },
+ { value: 'trash', label: __( 'Trash' ), icon: trash },
+];
+
+export default STATUSES;
diff --git a/packages/fields/src/fields/status/status-view.tsx b/packages/fields/src/fields/status/status-view.tsx
new file mode 100644
index 00000000000000..4f3c0547431ac9
--- /dev/null
+++ b/packages/fields/src/fields/status/status-view.tsx
@@ -0,0 +1,28 @@
+/**
+ * WordPress dependencies
+ */
+import { __experimentalHStack as HStack, Icon } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import type { BasePost } from '../../types';
+import STATUSES from './status-elements';
+
+function StatusView( { item }: { item: BasePost } ) {
+ const status = STATUSES.find( ( { value } ) => value === item.status );
+ const label = status?.label || item.status;
+ const icon = status?.icon;
+ return (
+
+ { icon && (
+
+
+
+ ) }
+ { label }
+
+ );
+}
+
+export default StatusView;
diff --git a/packages/fields/src/style.scss b/packages/fields/src/style.scss
new file mode 100644
index 00000000000000..1639f455ba093e
--- /dev/null
+++ b/packages/fields/src/style.scss
@@ -0,0 +1,2 @@
+@import "./fields/slug/style.scss";
+@import "./fields/featured-image/style.scss";
diff --git a/packages/fields/src/styles.scss b/packages/fields/src/styles.scss
deleted file mode 100644
index cdb130337f1cd9..00000000000000
--- a/packages/fields/src/styles.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import "./fields/slug/style.scss";
diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md
index 488fcc77d7541d..6963ed57a48aed 100644
--- a/packages/interactivity/CHANGELOG.md
+++ b/packages/interactivity/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Bug Fixes
+
+- Fix property modification from inherited context two or more levels above ([#66872](https://github.com/WordPress/gutenberg/pull/66872)).
+
## 6.11.0 (2024-10-30)
### Bug Fixes
diff --git a/packages/interactivity/src/proxies/context.ts b/packages/interactivity/src/proxies/context.ts
index 64517c91a6940e..8d5bc54a8b831a 100644
--- a/packages/interactivity/src/proxies/context.ts
+++ b/packages/interactivity/src/proxies/context.ts
@@ -38,6 +38,9 @@ const contextHandlers: ProxyHandler< object > = {
getOwnPropertyDescriptor: ( target, key ) =>
descriptor( target, key ) ||
descriptor( contextObjectToFallback.get( target ), key ),
+ has: ( target, key ) =>
+ Reflect.has( target, key ) ||
+ Reflect.has( contextObjectToFallback.get( target ), key ),
};
/**
diff --git a/packages/interactivity/src/proxies/test/context-proxy.ts b/packages/interactivity/src/proxies/test/context-proxy.ts
index 1e4a969d0f9dc6..6ae041d34dbc08 100644
--- a/packages/interactivity/src/proxies/test/context-proxy.ts
+++ b/packages/interactivity/src/proxies/test/context-proxy.ts
@@ -137,6 +137,24 @@ describe( 'Interactivity API', () => {
expect( context.a ).toBe( 2 );
expect( state.a ).toBe( 2 );
} );
+
+ it( "should modify props inherited from fallback's ancestors", () => {
+ const ancestor: any = proxifyContext(
+ { ancestorProp: 'ancestor' },
+ {}
+ );
+ const fallback: any = proxifyContext(
+ { fallbackProp: 'fallback' },
+ ancestor
+ );
+ const context: any = proxifyContext( {}, fallback );
+
+ context.ancestorProp = 'modified';
+
+ expect( context.ancestorProp ).toBe( 'modified' );
+ expect( fallback.ancestorProp ).toBe( 'modified' );
+ expect( ancestor.ancestorProp ).toBe( 'modified' );
+ } );
} );
describe( 'computations', () => {
diff --git a/packages/patterns/src/components/duplicate-pattern-modal.js b/packages/patterns/src/components/duplicate-pattern-modal.js
index 2b51e82f22da4e..70fb0830e0f1cc 100644
--- a/packages/patterns/src/components/duplicate-pattern-modal.js
+++ b/packages/patterns/src/components/duplicate-pattern-modal.js
@@ -17,7 +17,7 @@ function getTermLabels( pattern, categories ) {
if ( pattern.type !== PATTERN_TYPES.user ) {
return categories.core
?.filter( ( category ) =>
- pattern.categories.includes( category.name )
+ pattern.categories?.includes( category.name )
)
.map( ( category ) => category.label );
}
diff --git a/packages/rich-text/src/component/event-listeners/index.js b/packages/rich-text/src/component/event-listeners/index.js
index 4f69db36db06a0..d4f760abdc735a 100644
--- a/packages/rich-text/src/component/event-listeners/index.js
+++ b/packages/rich-text/src/component/event-listeners/index.js
@@ -13,6 +13,7 @@ import formatBoundaries from './format-boundaries';
import deleteHandler from './delete';
import inputAndSelection from './input-and-selection';
import selectionChangeCompat from './selection-change-compat';
+import { preventFocusCapture } from './prevent-focus-capture';
const allEventListeners = [
copyHandler,
@@ -21,6 +22,7 @@ const allEventListeners = [
deleteHandler,
inputAndSelection,
selectionChangeCompat,
+ preventFocusCapture,
];
export function useEventListeners( props ) {
diff --git a/packages/rich-text/src/component/event-listeners/prevent-focus-capture.js b/packages/rich-text/src/component/event-listeners/prevent-focus-capture.js
new file mode 100644
index 00000000000000..8dc97f73673ce6
--- /dev/null
+++ b/packages/rich-text/src/component/event-listeners/prevent-focus-capture.js
@@ -0,0 +1,44 @@
+/**
+ * Prevents focus from being captured by the element when clicking _outside_
+ * around the element. This may happen when the parent element is flex.
+ * @see https://github.com/WordPress/gutenberg/pull/65857
+ * @see https://github.com/WordPress/gutenberg/pull/66402
+ */
+export function preventFocusCapture() {
+ return ( element ) => {
+ const { ownerDocument } = element;
+ const { defaultView } = ownerDocument;
+
+ let value = null;
+
+ function onPointerDown( event ) {
+ // Abort if the event is default prevented, we will not get a pointer up event.
+ if ( event.defaultPrevented ) {
+ return;
+ }
+ if ( event.target === element ) {
+ return;
+ }
+ if ( ! event.target.contains( element ) ) {
+ return;
+ }
+ value = element.getAttribute( 'contenteditable' );
+ element.setAttribute( 'contenteditable', 'false' );
+ defaultView.getSelection().removeAllRanges();
+ }
+
+ function onPointerUp() {
+ if ( value !== null ) {
+ element.setAttribute( 'contenteditable', value );
+ value = null;
+ }
+ }
+
+ defaultView.addEventListener( 'pointerdown', onPointerDown );
+ defaultView.addEventListener( 'pointerup', onPointerUp );
+ return () => {
+ defaultView.removeEventListener( 'pointerdown', onPointerDown );
+ defaultView.removeEventListener( 'pointerup', onPointerUp );
+ };
+ };
+}
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index 50a0af4f9b3ff3..f77086cf0dd046 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -4387,6 +4387,178 @@ public function test_block_style_variations_with_invalid_properties() {
$this->assertSameSetsWithIndex( $expected, $actual );
}
+ public function test_block_style_variations_with_inner_blocks_and_elements() {
+ wp_set_current_user( static::$administrator_id );
+ gutenberg_register_block_style(
+ array( 'core/group' ),
+ array(
+ 'name' => 'custom-group',
+ 'label' => 'Custom Group',
+ )
+ );
+
+ $expected = array(
+ 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
+ 'styles' => array(
+ 'blocks' => array(
+ 'core/group' => array(
+ 'color' => array(
+ 'background' => 'blue',
+ ),
+ 'variations' => array(
+ 'custom-group' => array(
+ 'color' => array(
+ 'background' => 'purple',
+ ),
+ 'blocks' => array(
+ 'core/paragraph' => array(
+ 'color' => array(
+ 'text' => 'red',
+ ),
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'blue',
+ ),
+ ':hover' => array(
+ 'color' => array(
+ 'text' => 'green',
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'core/heading' => array(
+ 'typography' => array(
+ 'fontSize' => '24px',
+ ),
+ ),
+ ),
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'yellow',
+ ),
+ ':hover' => array(
+ 'color' => array(
+ 'text' => 'orange',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $expected );
+
+ // The sanitization processes blocks in a specific order which might differ to the theme.json input.
+ $this->assertEqualsCanonicalizing(
+ $expected,
+ $actual,
+ 'Block style variations data does not match when inner blocks or element styles present'
+ );
+ }
+
+ public function test_block_style_variations_with_invalid_inner_block_or_element_styles() {
+ wp_set_current_user( static::$administrator_id );
+ gutenberg_register_block_style(
+ array( 'core/group' ),
+ array(
+ 'name' => 'custom-group',
+ 'label' => 'Custom Group',
+ )
+ );
+
+ $input = array(
+ 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
+ 'styles' => array(
+ 'blocks' => array(
+ 'core/group' => array(
+ 'variations' => array(
+ 'custom-group' => array(
+ 'blocks' => array(
+ 'core/paragraph' => array(
+ 'color' => array(
+ 'text' => 'red',
+ ),
+ 'typography' => array(
+ 'fontSize' => 'alert(1)', // Should be removed.
+ ),
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'blue',
+ ),
+ 'css' => 'unsafe-value', // Should be removed.
+ ),
+ ),
+ 'custom' => 'unsafe-value', // Should be removed.
+ ),
+ ),
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'yellow',
+ ),
+ 'javascript' => 'alert(1)', // Should be removed.
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $expected = array(
+ 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
+ 'styles' => array(
+ 'blocks' => array(
+ 'core/group' => array(
+ 'variations' => array(
+ 'custom-group' => array(
+ 'blocks' => array(
+ 'core/paragraph' => array(
+ 'color' => array(
+ 'text' => 'red',
+ ),
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'blue',
+ ),
+ ),
+ ),
+ ),
+ ),
+ 'elements' => array(
+ 'link' => array(
+ 'color' => array(
+ 'text' => 'yellow',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $input );
+
+ // The sanitization processes blocks in a specific order which might differ to the theme.json input.
+ $this->assertEqualsCanonicalizing(
+ $expected,
+ $actual,
+ 'Insecure properties were not removed from block style variation inner block types or elements'
+ );
+ }
+
/**
* Tests generating the spacing presets array based on the spacing scale provided.
*
diff --git a/test/e2e/specs/editor/various/multi-block-selection.spec.js b/test/e2e/specs/editor/various/multi-block-selection.spec.js
index 9148c316078a4a..41fb120023541a 100644
--- a/test/e2e/specs/editor/various/multi-block-selection.spec.js
+++ b/test/e2e/specs/editor/various/multi-block-selection.spec.js
@@ -728,7 +728,7 @@ test.describe( 'Multi-block selection (@firefox, @webkit)', () => {
] );
} );
- test( 'should clear selection when clicking next to blocks (-firefox)', async ( {
+ test( 'should clear selection when clicking next to blocks', async ( {
page,
editor,
multiBlockSelectionUtils,
@@ -752,16 +752,13 @@ test.describe( 'Multi-block selection (@firefox, @webkit)', () => {
name: 'Block: Paragraph',
} )
.filter( { hasText: '1' } );
+ // For some reason in Chrome it requires two clicks, even though it
+ // doesn't when testing manually.
await paragraph1.click( {
position: { x: -1, y: 0 },
// Use force since it's outside the bounding box of the element.
force: true,
} );
-
- await expect
- .poll( multiBlockSelectionUtils.getSelectedFlatIndices )
- .toEqual( [ 1 ] );
-
await paragraph1.click( {
position: { x: -1, y: 0 },
// Use force since it's outside the bounding box of the element.
diff --git a/test/e2e/specs/editor/various/splitting-merging.spec.js b/test/e2e/specs/editor/various/splitting-merging.spec.js
index eba9f1d3163fd5..146039a7c7d1bf 100644
--- a/test/e2e/specs/editor/various/splitting-merging.spec.js
+++ b/test/e2e/specs/editor/various/splitting-merging.spec.js
@@ -393,6 +393,11 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => {
attributes: { content: 'heading', level: 2 },
innerBlocks: [],
};
+ const paragraphWithContent = {
+ name: 'core/paragraph',
+ attributes: { content: 'heading', dropCap: false },
+ innerBlocks: [],
+ };
const placeholderBlock = { name: 'core/separator' };
await editor.insertBlock( {
name: 'core/group',
@@ -407,6 +412,9 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => {
.getByRole( 'document', { name: 'Empty block' } )
.focus();
+ // Remove the alignment.
+ await page.keyboard.press( 'Backspace' );
+ // Remove the empty paragraph block.
await page.keyboard.press( 'Backspace' );
await expect
.poll( editor.getBlocks, 'Remove the default empty block' )
@@ -422,8 +430,7 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => {
},
] );
- // Move the caret to the beginning of the empty heading block.
- await page.keyboard.press( 'ArrowDown' );
+ // Convert the heading to a default block.
await page.keyboard.press( 'Backspace' );
await expect
.poll(
@@ -441,6 +448,9 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => {
],
},
] );
+ // Remove the alignment.
+ await page.keyboard.press( 'Backspace' );
+ // Remove the empty default block.
await page.keyboard.press( 'Backspace' );
await expect.poll( editor.getBlocks ).toEqual( [
{
@@ -453,17 +463,16 @@ test.describe( 'splitting and merging blocks (@firefox, @webkit)', () => {
},
] );
- // Move the caret to the beginning of the "heading" heading block.
- await page.keyboard.press( 'ArrowDown' );
+ // Convert a non-empty non-default block to a default block.
await page.keyboard.press( 'Backspace' );
await expect
.poll( editor.getBlocks, 'Lift the non-empty non-default block' )
.toEqual( [
- headingWithContent,
{
name: 'core/group',
attributes: { tagName: 'div' },
innerBlocks: [
+ paragraphWithContent,
expect.objectContaining( placeholderBlock ),
],
},
diff --git a/test/e2e/specs/site-editor/style-book.spec.js b/test/e2e/specs/site-editor/style-book.spec.js
index c94049872edcf6..d860b05bc8f06d 100644
--- a/test/e2e/specs/site-editor/style-book.spec.js
+++ b/test/e2e/specs/site-editor/style-book.spec.js
@@ -52,14 +52,10 @@ test.describe( 'Style Book', () => {
'[name="style-book-canvas"]'
);
+ // In the Overview tab, expect a button for the main typography section.
await expect(
styleBookIframe.getByRole( 'button', {
- name: 'Open Headings styles in Styles panel',
- } )
- ).toBeVisible();
- await expect(
- styleBookIframe.getByRole( 'button', {
- name: 'Open Paragraph styles in Styles panel',
+ name: 'Open Typography styles in Styles panel',
} )
).toBeVisible();
@@ -83,13 +79,13 @@ test.describe( 'Style Book', () => {
await page
.frameLocator( '[name="style-book-canvas"]' )
.getByRole( 'button', {
- name: 'Open Headings styles in Styles panel',
+ name: 'Open Image styles in Styles panel',
} )
.click();
await expect(
page.locator(
- 'role=region[name="Editor settings"i] >> role=heading[name="Heading"i]'
+ 'role=region[name="Editor settings"i] >> role=heading[name="Image"i]'
)
).toBeVisible();
} );
@@ -103,7 +99,7 @@ test.describe( 'Style Book', () => {
await page
.frameLocator( '[name="style-book-canvas"]' )
.getByRole( 'button', {
- name: 'Open Quote styles in Styles panel',
+ name: 'Open Pullquote styles in Styles panel',
} )
.click();
|