From b5c694085671c8e22a070bdff6d13f8c4fc6a66b Mon Sep 17 00:00:00 2001
From: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date: Mon, 11 Sep 2023 13:22:11 +0200
Subject: [PATCH 01/42] Remove unnecessary padding-right on the dismissible
notice (#52240)
---
packages/components/src/notice/style.scss | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss
index 23378cefe1ec63..ebd76d35ce7065 100644
--- a/packages/components/src/notice/style.scss
+++ b/packages/components/src/notice/style.scss
@@ -9,7 +9,6 @@
align-items: center;
&.is-dismissible {
- padding-right: 36px;
position: relative;
}
From bb4ed887caa415e3c724094f55914ded2deec02e Mon Sep 17 00:00:00 2001
From: Chintan hingrajiya <38949444+chintu51@users.noreply.github.com>
Date: Mon, 11 Sep 2023 16:55:30 +0530
Subject: [PATCH 02/42] Add missing useState import in Border Control
documentation. (#49476)
* Fix retrieving autosaves when using a custom rest_namespace
When specifying `rest_namespace` in a custom post type, a hardcoded `wp/v2` in `getAutosaves` results in a 404
* linter fixes for rest namespace resolution in autosave resolver
* try to fix autosave resolver and namespaces not present
* update resolvers for autosaves endpoint
* Fix lints.
* Add missing useState import in Border Control documentation.
---------
Co-authored-by: Tom J Nowell
Co-authored-by: Jonny Harris
Co-authored-by: Jonny Harris
---
packages/components/src/border-control/border-control/README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/components/src/border-control/border-control/README.md b/packages/components/src/border-control/border-control/README.md
index 901859df2395e9..14b3df6afaf6c9 100644
--- a/packages/components/src/border-control/border-control/README.md
+++ b/packages/components/src/border-control/border-control/README.md
@@ -22,6 +22,7 @@ a "shape" abstraction.
```jsx
import { __experimentalBorderControl as BorderControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
const colors = [
{ name: 'Blue 20', color: '#72aee6' },
From d9208b8ee4037d8b7b4e2801b4ba7c2ff13bc094 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Mon, 11 Sep 2023 12:34:13 +0100
Subject: [PATCH 03/42] Extract undo/redo as a separate package (#54292)
Co-authored-by: Ella <4710635+ellatrix@users.noreply.github.com>
---
docs/manifest.json | 6 +
docs/reference-guides/data/data-core.md | 2 +-
package-lock.json | 26 ++
package.json | 1 +
packages/core-data/README.md | 2 +-
packages/core-data/package.json | 1 +
packages/core-data/src/actions.js | 49 ++--
packages/core-data/src/private-selectors.ts | 21 +-
packages/core-data/src/reducer.js | 182 ++-----------
packages/core-data/src/selectors.ts | 53 +---
packages/core-data/src/test/reducer.js | 233 ----------------
packages/core-data/src/test/selectors.js | 54 ----
packages/core-data/tsconfig.json | 1 +
.../lib/util.js | 6 +-
packages/undo-manager/.npmrc | 1 +
packages/undo-manager/CHANGELOG.md | 4 +
packages/undo-manager/README.md | 33 +++
packages/undo-manager/package.json | 37 +++
packages/undo-manager/src/index.js | 171 ++++++++++++
packages/undo-manager/src/test/index.js | 257 ++++++++++++++++++
packages/undo-manager/src/types.ts | 19 ++
packages/undo-manager/tsconfig.json | 10 +
tools/webpack/packages.js | 6 +-
tsconfig.json | 1 +
24 files changed, 651 insertions(+), 525 deletions(-)
create mode 100644 packages/undo-manager/.npmrc
create mode 100644 packages/undo-manager/CHANGELOG.md
create mode 100644 packages/undo-manager/README.md
create mode 100644 packages/undo-manager/package.json
create mode 100644 packages/undo-manager/src/index.js
create mode 100644 packages/undo-manager/src/test/index.js
create mode 100644 packages/undo-manager/src/types.ts
create mode 100644 packages/undo-manager/tsconfig.json
diff --git a/docs/manifest.json b/docs/manifest.json
index 75ae7d0b31f67d..354a81a70abba6 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1919,6 +1919,12 @@
"markdown_source": "../packages/token-list/README.md",
"parent": "packages"
},
+ {
+ "title": "@wordpress/undo-manager",
+ "slug": "packages-undo-manager",
+ "markdown_source": "../packages/undo-manager/README.md",
+ "parent": "packages"
+ },
{
"title": "@wordpress/url",
"slug": "packages-url",
diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md
index f2bc3374f9e721..95401834ad4391 100644
--- a/docs/reference-guides/data/data-core.md
+++ b/docs/reference-guides/data/data-core.md
@@ -371,7 +371,7 @@ _Usage_
_Parameters_
-- _state_ `State`: Editor state.
+- _state_ Editor state.
_Returns_
diff --git a/package-lock.json b/package-lock.json
index 29d2296841dee7..340130affbd181 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -74,6 +74,7 @@
"@wordpress/style-engine": "file:packages/style-engine",
"@wordpress/sync": "file:packages/sync",
"@wordpress/token-list": "file:packages/token-list",
+ "@wordpress/undo-manager": "file:packages/undo-manager",
"@wordpress/url": "file:packages/url",
"@wordpress/viewport": "file:packages/viewport",
"@wordpress/warning": "file:packages/warning",
@@ -15655,6 +15656,10 @@
"resolved": "packages/token-list",
"link": true
},
+ "node_modules/@wordpress/undo-manager": {
+ "resolved": "packages/undo-manager",
+ "link": true
+ },
"node_modules/@wordpress/url": {
"resolved": "packages/url",
"link": true
@@ -55009,6 +55014,7 @@
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
"@wordpress/private-apis": "file:../private-apis",
"@wordpress/sync": "file:../sync",
+ "@wordpress/undo-manager": "file:../undo-manager",
"@wordpress/url": "file:../url",
"change-case": "^4.1.2",
"equivalent-key-map": "^0.2.2",
@@ -56561,6 +56567,18 @@
"node": ">=12"
}
},
+ "packages/undo-manager": {
+ "name": "@wordpress/undo-manager",
+ "version": "0.1.0",
+ "license": "GPL-2.0-or-later",
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "@wordpress/is-shallow-equal": "file:../is-shallow-equal"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"packages/url": {
"name": "@wordpress/url",
"version": "3.42.0",
@@ -67892,6 +67910,7 @@
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
"@wordpress/private-apis": "file:../private-apis",
"@wordpress/sync": "file:../sync",
+ "@wordpress/undo-manager": "file:../undo-manager",
"@wordpress/url": "file:../url",
"change-case": "^4.1.2",
"equivalent-key-map": "^0.2.2",
@@ -68899,6 +68918,13 @@
"@babel/runtime": "^7.16.0"
}
},
+ "@wordpress/undo-manager": {
+ "version": "file:packages/undo-manager",
+ "requires": {
+ "@babel/runtime": "^7.16.0",
+ "@wordpress/is-shallow-equal": "file:../is-shallow-equal"
+ }
+ },
"@wordpress/url": {
"version": "file:packages/url",
"requires": {
diff --git a/package.json b/package.json
index 7d46a0c7273a20..1cd8535bfc803c 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,7 @@
"@wordpress/style-engine": "file:packages/style-engine",
"@wordpress/sync": "file:packages/sync",
"@wordpress/token-list": "file:packages/token-list",
+ "@wordpress/undo-manager": "file:packages/undo-manager",
"@wordpress/url": "file:packages/url",
"@wordpress/viewport": "file:packages/viewport",
"@wordpress/warning": "file:packages/warning",
diff --git a/packages/core-data/README.md b/packages/core-data/README.md
index c778b724149ef3..18e131cd7ab6f1 100644
--- a/packages/core-data/README.md
+++ b/packages/core-data/README.md
@@ -548,7 +548,7 @@ _Usage_
_Parameters_
-- _state_ `State`: Editor state.
+- _state_ Editor state.
_Returns_
diff --git a/packages/core-data/package.json b/packages/core-data/package.json
index 80bc41ff0a5afe..1f83dc9814400c 100644
--- a/packages/core-data/package.json
+++ b/packages/core-data/package.json
@@ -43,6 +43,7 @@
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
"@wordpress/private-apis": "file:../private-apis",
"@wordpress/sync": "file:../sync",
+ "@wordpress/undo-manager": "file:../undo-manager",
"@wordpress/url": "file:../url",
"change-case": "^4.1.2",
"equivalent-key-map": "^0.2.2",
diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js
index 1969d2cd717a2a..a79d1236682582 100644
--- a/packages/core-data/src/actions.js
+++ b/packages/core-data/src/actions.js
@@ -391,20 +391,29 @@ export const editEntityRecord =
edit.edits
);
} else {
+ if ( ! options.undoIgnore ) {
+ select.getUndoManager().addRecord(
+ [
+ {
+ id: { kind, name, recordId },
+ changes: Object.keys( edits ).reduce(
+ ( acc, key ) => {
+ acc[ key ] = {
+ from: editedRecord[ key ],
+ to: edits[ key ],
+ };
+ return acc;
+ },
+ {}
+ ),
+ },
+ ],
+ options.isCached
+ );
+ }
dispatch( {
type: 'EDIT_ENTITY_RECORD',
...edit,
- meta: {
- undo: ! options.undoIgnore && {
- ...edit,
- // Send the current values for things like the first undo stack entry.
- edits: Object.keys( edits ).reduce( ( acc, key ) => {
- acc[ key ] = editedRecord[ key ];
- return acc;
- }, {} ),
- isCached: options.isCached,
- },
- },
} );
}
};
@@ -416,13 +425,14 @@ export const editEntityRecord =
export const undo =
() =>
( { select, dispatch } ) => {
- const undoEdit = select.getUndoEdits();
+ const undoEdit = select.getUndoManager().getUndoRecord();
if ( ! undoEdit ) {
return;
}
+ select.getUndoManager().undo();
dispatch( {
type: 'UNDO',
- stackedEdits: undoEdit,
+ record: undoEdit,
} );
};
@@ -433,13 +443,14 @@ export const undo =
export const redo =
() =>
( { select, dispatch } ) => {
- const redoEdit = select.getRedoEdits();
+ const redoEdit = select.getUndoManager().getRedoRecord();
if ( ! redoEdit ) {
return;
}
+ select.getUndoManager().redo();
dispatch( {
type: 'REDO',
- stackedEdits: redoEdit,
+ record: redoEdit,
} );
};
@@ -448,9 +459,11 @@ export const redo =
*
* @return {Object} Action object.
*/
-export function __unstableCreateUndoLevel() {
- return { type: 'CREATE_UNDO_LEVEL' };
-}
+export const __unstableCreateUndoLevel =
+ () =>
+ ( { select } ) => {
+ select.getUndoManager().addRecord();
+ };
/**
* Action triggered to save an entity record.
diff --git a/packages/core-data/src/private-selectors.ts b/packages/core-data/src/private-selectors.ts
index 1e253b900e1cbb..94aa00e1c8de45 100644
--- a/packages/core-data/src/private-selectors.ts
+++ b/packages/core-data/src/private-selectors.ts
@@ -1,9 +1,8 @@
/**
* Internal dependencies
*/
-import type { State, UndoEdit } from './selectors';
+import type { State } from './selectors';
-type Optional< T > = T | undefined;
type EntityRecordKey = string | number;
/**
@@ -12,22 +11,10 @@ type EntityRecordKey = string | number;
*
* @param state State tree.
*
- * @return The edit.
+ * @return The undo manager.
*/
-export function getUndoEdits( state: State ): Optional< UndoEdit[] > {
- return state.undo.list[ state.undo.list.length - 1 + state.undo.offset ];
-}
-
-/**
- * Returns the next edit from the current undo offset
- * for the entity records edits history, if any.
- *
- * @param state State tree.
- *
- * @return The edit.
- */
-export function getRedoEdits( state: State ): Optional< UndoEdit[] > {
- return state.undo.list[ state.undo.list.length + state.undo.offset ];
+export function getUndoManager( state: State ) {
+ return state.undoManager;
}
/**
diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js
index 20755dad4be8d2..f097d07d047746 100644
--- a/packages/core-data/src/reducer.js
+++ b/packages/core-data/src/reducer.js
@@ -8,7 +8,7 @@ import fastDeepEqual from 'fast-deep-equal/es6';
*/
import { compose } from '@wordpress/compose';
import { combineReducers } from '@wordpress/data';
-import isShallowEqual from '@wordpress/is-shallow-equal';
+import { createUndoManager } from '@wordpress/undo-manager';
/**
* Internal dependencies
@@ -185,22 +185,25 @@ export function themeGlobalStyleVariations( state = {}, action ) {
const withMultiEntityRecordEdits = ( reducer ) => ( state, action ) => {
if ( action.type === 'UNDO' || action.type === 'REDO' ) {
- const { stackedEdits } = action;
+ const { record } = action;
let newState = state;
- stackedEdits.forEach(
- ( { kind, name, recordId, property, from, to } ) => {
- newState = reducer( newState, {
- type: 'EDIT_ENTITY_RECORD',
- kind,
- name,
- recordId,
- edits: {
- [ property ]: action.type === 'UNDO' ? from : to,
+ record.forEach( ( { id: { kind, name, recordId }, changes } ) => {
+ newState = reducer( newState, {
+ type: 'EDIT_ENTITY_RECORD',
+ kind,
+ name,
+ recordId,
+ edits: Object.entries( changes ).reduce(
+ ( acc, [ key, value ] ) => {
+ acc[ key ] =
+ action.type === 'UNDO' ? value.from : value.to;
+ return acc;
},
- } );
- }
- );
+ {}
+ ),
+ } );
+ } );
return newState;
}
@@ -435,151 +438,19 @@ export const entities = ( state = {}, action ) => {
};
/**
- * @typedef {Object} UndoStateMeta
- *
- * @property {number} list The undo stack.
- * @property {number} offset Where in the undo stack we are.
- * @property {Object} cache Cache of unpersisted edits.
- */
-
-/** @typedef {Array & UndoStateMeta} UndoState */
-
-/**
- * @type {UndoState}
- *
- * @todo Given how we use this we might want to make a custom class for it.
- */
-const UNDO_INITIAL_STATE = { list: [], offset: 0 };
-
-/**
- * Reducer keeping track of entity edit undo history.
- *
- * @param {UndoState} state Current state.
- * @param {Object} action Dispatched action.
- *
- * @return {UndoState} Updated state.
+ * @type {UndoManager}
*/
-export function undo( state = UNDO_INITIAL_STATE, action ) {
- const omitPendingRedos = ( currentState ) => {
- return {
- ...currentState,
- list: currentState.list.slice(
- 0,
- currentState.offset || undefined
- ),
- offset: 0,
- };
- };
-
- const appendCachedEditsToLastUndo = ( currentState ) => {
- if ( ! currentState.cache ) {
- return currentState;
- }
-
- let nextState = {
- ...currentState,
- list: [ ...currentState.list ],
- };
- nextState = omitPendingRedos( nextState );
- const previousUndoState = nextState.list.pop();
- const updatedUndoState = currentState.cache.reduce(
- appendEditToStack,
- previousUndoState
- );
- nextState.list.push( updatedUndoState );
-
- return {
- ...nextState,
- cache: undefined,
- };
- };
-
- const appendEditToStack = (
- stack = [],
- { kind, name, recordId, property, from, to }
- ) => {
- const existingEditIndex = stack?.findIndex(
- ( { kind: k, name: n, recordId: r, property: p } ) => {
- return (
- k === kind && n === name && r === recordId && p === property
- );
- }
- );
- const nextStack = [ ...stack ];
- if ( existingEditIndex !== -1 ) {
- // If the edit is already in the stack leave the initial "from" value.
- nextStack[ existingEditIndex ] = {
- ...nextStack[ existingEditIndex ],
- to,
- };
- } else {
- nextStack.push( {
- kind,
- name,
- recordId,
- property,
- from,
- to,
- } );
- }
- return nextStack;
- };
+export function undoManager( state = createUndoManager() ) {
+ return state;
+}
+export function editsReference( state = {}, action ) {
switch ( action.type ) {
- case 'CREATE_UNDO_LEVEL':
- return appendCachedEditsToLastUndo( state );
-
+ case 'EDIT_ENTITY_RECORD':
case 'UNDO':
- case 'REDO': {
- const nextState = appendCachedEditsToLastUndo( state );
- return {
- ...nextState,
- offset: state.offset + ( action.type === 'UNDO' ? -1 : 1 ),
- };
- }
-
- case 'EDIT_ENTITY_RECORD': {
- if ( ! action.meta.undo ) {
- return state;
- }
-
- const edits = Object.keys( action.edits ).map( ( key ) => {
- return {
- kind: action.kind,
- name: action.name,
- recordId: action.recordId,
- property: key,
- from: action.meta.undo.edits[ key ],
- to: action.edits[ key ],
- };
- } );
-
- if ( action.meta.undo.isCached ) {
- return {
- ...state,
- cache: edits.reduce( appendEditToStack, state.cache ),
- };
- }
-
- let nextState = omitPendingRedos( state );
- nextState = appendCachedEditsToLastUndo( nextState );
- nextState = { ...nextState, list: [ ...nextState.list ] };
- // When an edit is a function it's an optimization to avoid running some expensive operation.
- // We can't rely on the function references being the same so we opt out of comparing them here.
- const comparisonUndoEdits = Object.values(
- action.meta.undo.edits
- ).filter( ( edit ) => typeof edit !== 'function' );
- const comparisonEdits = Object.values( action.edits ).filter(
- ( edit ) => typeof edit !== 'function'
- );
- if ( ! isShallowEqual( comparisonUndoEdits, comparisonEdits ) ) {
- nextState.list.push( edits );
- }
-
- return nextState;
- }
+ case 'REDO':
+ return {};
}
-
return state;
}
@@ -704,7 +575,8 @@ export default combineReducers( {
themeGlobalStyleRevisions,
taxonomies,
entities,
- undo,
+ editsReference,
+ undoManager,
embedPreviews,
userPermissions,
autosaves,
diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts
index 377134ab7c9a3d..e4fb2eada0cf87 100644
--- a/packages/core-data/src/selectors.ts
+++ b/packages/core-data/src/selectors.ts
@@ -22,7 +22,7 @@ import {
setNestedValue,
} from './utils';
import type * as ET from './entity-types';
-import { getUndoEdits, getRedoEdits } from './private-selectors';
+import type { UndoManager } from '@wordpress/undo-manager';
// This is an incomplete, high-level approximation of the State type.
// It makes the selectors slightly more safe, but is intended to evolve
@@ -40,7 +40,7 @@ export interface State {
themeBaseGlobalStyles: Record< string, Object >;
themeGlobalStyleVariations: Record< string, string >;
themeGlobalStyleRevisions: Record< number, Object >;
- undo: UndoState;
+ undoManager: UndoManager;
userPermissions: Record< string, boolean >;
users: UserState;
navigationFallbackId: EntityRecordKey;
@@ -74,20 +74,6 @@ interface EntityConfig {
kind: string;
}
-export interface UndoEdit {
- name: string;
- kind: string;
- recordId: string;
- from: any;
- to: any;
-}
-
-interface UndoState {
- list: Array< UndoEdit[] >;
- offset: number;
- cache: UndoEdit[];
-}
-
interface UserState {
queries: Record< string, EntityRecordKey[] >;
byId: Record< EntityRecordKey, ET.User< 'edit' > >;
@@ -875,21 +861,6 @@ export function getLastEntityDeleteError(
?.error;
}
-/**
- * Returns the current undo offset for the
- * entity records edits history. The offset
- * represents how many items from the end
- * of the history stack we are at. 0 is the
- * last edit, -1 is the second last, and so on.
- *
- * @param state State tree.
- *
- * @return The current undo offset.
- */
-function getCurrentUndoOffset( state: State ): number {
- return state.undo.offset;
-}
-
/**
* Returns the previous edit from the current undo offset
* for the entity records edits history, if any.
@@ -904,9 +875,7 @@ export function getUndoEdit( state: State ): Optional< any > {
deprecated( "select( 'core' ).getUndoEdit()", {
since: '6.3',
} );
- return state.undo.list[
- state.undo.list.length - 2 + getCurrentUndoOffset( state )
- ]?.[ 0 ];
+ return undefined;
}
/**
@@ -923,9 +892,7 @@ export function getRedoEdit( state: State ): Optional< any > {
deprecated( "select( 'core' ).getRedoEdit()", {
since: '6.3',
} );
- return state.undo.list[
- state.undo.list.length + getCurrentUndoOffset( state )
- ]?.[ 0 ];
+ return undefined;
}
/**
@@ -937,7 +904,7 @@ export function getRedoEdit( state: State ): Optional< any > {
* @return Whether there is a previous edit or not.
*/
export function hasUndo( state: State ): boolean {
- return Boolean( getUndoEdits( state ) );
+ return Boolean( state.undoManager.getUndoRecord() );
}
/**
@@ -949,7 +916,7 @@ export function hasUndo( state: State ): boolean {
* @return Whether there is a next edit or not.
*/
export function hasRedo( state: State ): boolean {
- return Boolean( getRedoEdits( state ) );
+ return Boolean( state.undoManager.getRedoRecord() );
}
/**
@@ -1163,11 +1130,9 @@ export const hasFetchedAutosaves = createRegistrySelector(
*
* @return A value whose reference will change only when an edit occurs.
*/
-export const getReferenceByDistinctEdits = createSelector(
- // This unused state argument is listed here for the documentation generating tool (docgen).
- ( state: State ) => [],
- ( state: State ) => [ state.undo.list.length, state.undo.offset ]
-);
+export function getReferenceByDistinctEdits( state ) {
+ return state.editsReference;
+}
/**
* Retrieve the frontend template used for a given link.
diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js
index 7fac52c33c4b36..4142f65af4c7c4 100644
--- a/packages/core-data/src/test/reducer.js
+++ b/packages/core-data/src/test/reducer.js
@@ -9,7 +9,6 @@ import deepFreeze from 'deep-freeze';
import {
terms,
entities,
- undo,
embedPreviews,
userPermissions,
autosaves,
@@ -142,238 +141,6 @@ describe( 'entities', () => {
} );
} );
-describe( 'undo', () => {
- let lastValues;
- let undoState;
- let expectedUndoState;
-
- const createExpectedDiff = ( property, { from, to } ) => ( {
- kind: 'someKind',
- name: 'someName',
- recordId: 'someRecordId',
- property,
- from,
- to,
- } );
- const createNextEditAction = ( edits, isCached ) => {
- let action = {
- kind: 'someKind',
- name: 'someName',
- recordId: 'someRecordId',
- edits,
- };
- action = {
- type: 'EDIT_ENTITY_RECORD',
- ...action,
- meta: {
- undo: {
- isCached,
- edits: lastValues,
- },
- },
- };
- lastValues = { ...lastValues, ...edits };
- return action;
- };
- const createNextUndoState = ( ...args ) => {
- let action = {};
- if ( args[ 0 ] === 'isUndo' || args[ 0 ] === 'isRedo' ) {
- // We need to "apply" the undo level here and build
- // the action to move the offset.
- const lastEdits =
- undoState.list[
- undoState.list.length -
- ( args[ 0 ] === 'isUndo' ? 1 : 0 ) +
- undoState.offset
- ];
- lastEdits.forEach( ( { property, from, to } ) => {
- lastValues[ property ] = args[ 0 ] === 'isUndo' ? from : to;
- } );
- action = {
- type: args[ 0 ] === 'isUndo' ? 'UNDO' : 'REDO',
- };
- } else if ( args[ 0 ] === 'isCreate' ) {
- action = { type: 'CREATE_UNDO_LEVEL' };
- } else if ( args.length ) {
- action = createNextEditAction( ...args );
- }
- return deepFreeze( undo( undoState, action ) );
- };
- beforeEach( () => {
- lastValues = {};
- undoState = undefined;
- expectedUndoState = { list: [], offset: 0 };
- } );
-
- it( 'initializes', () => {
- expect( createNextUndoState() ).toEqual( expectedUndoState );
- } );
-
- it( 'stacks undo levels', () => {
- undoState = createNextUndoState();
-
- // Check that the first edit creates an undo level for the current state and
- // one for the new one.
- undoState = createNextUndoState( { value: 1 } );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: undefined, to: 1 } ),
- ] );
- expect( undoState ).toEqual( expectedUndoState );
-
- // Check that the second and third edits just create an undo level for
- // themselves.
- undoState = createNextUndoState( { value: 2 } );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: 1, to: 2 } ),
- ] );
- expect( undoState ).toEqual( expectedUndoState );
- undoState = createNextUndoState( { value: 3 } );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: 2, to: 3 } ),
- ] );
- expect( undoState ).toEqual( expectedUndoState );
- } );
-
- it( 'stacks multi-property undo levels', () => {
- undoState = createNextUndoState();
-
- undoState = createNextUndoState( { value: 1 } );
- undoState = createNextUndoState( { value2: 2 } );
- expectedUndoState.list.push(
- [ createExpectedDiff( 'value', { from: undefined, to: 1 } ) ],
- [ createExpectedDiff( 'value2', { from: undefined, to: 2 } ) ]
- );
- expect( undoState ).toEqual( expectedUndoState );
-
- // Check that that creating another undo level merges the "edits"
- undoState = createNextUndoState( { value: 2 } );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: 1, to: 2 } ),
- ] );
- expect( undoState ).toEqual( expectedUndoState );
- } );
-
- it( 'handles undos/redos', () => {
- undoState = createNextUndoState();
- undoState = createNextUndoState( { value: 1 } );
- undoState = createNextUndoState( { value: 2 } );
- undoState = createNextUndoState( { value: 3 } );
- expectedUndoState.list.push(
- [ createExpectedDiff( 'value', { from: undefined, to: 1 } ) ],
- [ createExpectedDiff( 'value', { from: 1, to: 2 } ) ],
- [ createExpectedDiff( 'value', { from: 2, to: 3 } ) ]
- );
- expect( undoState ).toEqual( expectedUndoState );
-
- // Check that undoing and redoing an equal
- // number of steps does not lose edits.
- undoState = createNextUndoState( 'isUndo' );
- expectedUndoState.offset--;
- expect( undoState ).toEqual( expectedUndoState );
- undoState = createNextUndoState( 'isUndo' );
- expectedUndoState.offset--;
- expect( undoState ).toEqual( expectedUndoState );
- undoState = createNextUndoState( 'isRedo' );
- expectedUndoState.offset++;
- expect( undoState ).toEqual( expectedUndoState );
- undoState = createNextUndoState( 'isRedo' );
- expectedUndoState.offset++;
- expect( undoState ).toEqual( expectedUndoState );
-
- // Check that another edit will go on top when there
- // is no undo level offset.
- undoState = createNextUndoState( { value: 4 } );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: 3, to: 4 } ),
- ] );
- expect( undoState ).toEqual( expectedUndoState );
-
- // Check that undoing and editing will slice of
- // all the levels after the current one.
- undoState = createNextUndoState( 'isUndo' );
- undoState = createNextUndoState( 'isUndo' );
-
- undoState = createNextUndoState( { value: 5 } );
- expectedUndoState.list.pop();
- expectedUndoState.list.pop();
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: 2, to: 5 } ),
- ] );
- expect( undoState ).toEqual( expectedUndoState );
- } );
-
- it( 'handles flattened undos/redos', () => {
- undoState = createNextUndoState();
- undoState = createNextUndoState( { value: 1 } );
- undoState = createNextUndoState( { transientValue: 2 }, true );
- undoState = createNextUndoState( { value: 3 } );
- expectedUndoState.list.push(
- [
- createExpectedDiff( 'value', { from: undefined, to: 1 } ),
- createExpectedDiff( 'transientValue', {
- from: undefined,
- to: 2,
- } ),
- ],
- [ createExpectedDiff( 'value', { from: 1, to: 3 } ) ]
- );
- expect( undoState ).toEqual( expectedUndoState );
- } );
-
- it( 'handles explicit undo level creation', () => {
- undoState = createNextUndoState();
-
- // Check that nothing happens if there are no pending
- // transient edits.
- undoState = createNextUndoState( { value: 1 } );
- undoState = createNextUndoState( 'isCreate' );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: undefined, to: 1 } ),
- ] );
- expect( undoState ).toEqual( expectedUndoState );
-
- // Check that transient edits are merged into the last
- // edits.
- undoState = createNextUndoState( { transientValue: 2 }, true );
- undoState = createNextUndoState( 'isCreate' );
- expectedUndoState.list[ expectedUndoState.list.length - 1 ].push(
- createExpectedDiff( 'transientValue', { from: undefined, to: 2 } )
- );
- expect( undoState ).toEqual( expectedUndoState );
-
- // Check that create after undo does nothing.
- undoState = createNextUndoState( { value: 3 } );
- undoState = createNextUndoState( 'isUndo' );
- undoState = createNextUndoState( 'isCreate' );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: 1, to: 3 } ),
- ] );
- expectedUndoState.offset = -1;
- expect( undoState ).toEqual( expectedUndoState );
- } );
-
- it( 'explicitly creates an undo level when undoing while there are pending transient edits', () => {
- undoState = createNextUndoState();
- undoState = createNextUndoState( { value: 1 } );
- undoState = createNextUndoState( { transientValue: 2 }, true );
- undoState = createNextUndoState( 'isUndo' );
- expectedUndoState.list.push( [
- createExpectedDiff( 'value', { from: undefined, to: 1 } ),
- createExpectedDiff( 'transientValue', { from: undefined, to: 2 } ),
- ] );
- expectedUndoState.offset--;
- expect( undoState ).toEqual( expectedUndoState );
- } );
-
- it( 'does not create new levels for the same function edits', () => {
- const value = () => {};
- undoState = createNextUndoState();
- undoState = createNextUndoState( { value } );
- undoState = createNextUndoState( { value: () => {} } );
- expect( undoState ).toEqual( expectedUndoState );
- } );
-} );
-
describe( 'embedPreviews()', () => {
it( 'returns an empty object by default', () => {
const state = embedPreviews( undefined, {} );
diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js
index 84fecc7d07cda9..161d0af4ea5bca 100644
--- a/packages/core-data/src/test/selectors.js
+++ b/packages/core-data/src/test/selectors.js
@@ -22,7 +22,6 @@ import {
getAutosave,
getAutosaves,
getCurrentUser,
- getReferenceByDistinctEdits,
} from '../selectors';
// getEntityRecord and __experimentalGetEntityRecordNoResolver selectors share the same tests.
describe.each( [
@@ -835,56 +834,3 @@ describe( 'getCurrentUser', () => {
expect( getCurrentUser( state ) ).toEqual( currentUser );
} );
} );
-
-describe( 'getReferenceByDistinctEdits', () => {
- it( 'should return referentially equal values across empty states', () => {
- const state = { undo: { list: [] } };
- expect( getReferenceByDistinctEdits( state ) ).toBe(
- getReferenceByDistinctEdits( state )
- );
-
- const beforeState = { undo: { list: [] } };
- const afterState = { undo: { list: [] } };
- expect( getReferenceByDistinctEdits( beforeState ) ).toBe(
- getReferenceByDistinctEdits( afterState )
- );
- } );
-
- it( 'should return referentially equal values across unchanging non-empty state', () => {
- const undoStates = { list: [ {} ] };
- const state = { undo: undoStates };
- expect( getReferenceByDistinctEdits( state ) ).toBe(
- getReferenceByDistinctEdits( state )
- );
-
- const beforeState = { undo: undoStates };
- const afterState = { undo: undoStates };
- expect( getReferenceByDistinctEdits( beforeState ) ).toBe(
- getReferenceByDistinctEdits( afterState )
- );
- } );
-
- describe( 'when adding edits', () => {
- it( 'should return referentially different values across changing states', () => {
- const beforeState = { undo: { list: [ {} ] } };
- beforeState.undo.offset = 0;
- const afterState = { undo: { list: [ {}, {} ] } };
- afterState.undo.offset = 1;
- expect( getReferenceByDistinctEdits( beforeState ) ).not.toBe(
- getReferenceByDistinctEdits( afterState )
- );
- } );
- } );
-
- describe( 'when using undo', () => {
- it( 'should return referentially different values across changing states', () => {
- const beforeState = { undo: { list: [ {}, {} ] } };
- beforeState.undo.offset = 1;
- const afterState = { undo: { list: [ {}, {} ] } };
- afterState.undo.offset = 0;
- expect( getReferenceByDistinctEdits( beforeState ) ).not.toBe(
- getReferenceByDistinctEdits( afterState )
- );
- } );
- } );
-} );
diff --git a/packages/core-data/tsconfig.json b/packages/core-data/tsconfig.json
index 031d697f8dbe6b..3fe698f758b54d 100644
--- a/packages/core-data/tsconfig.json
+++ b/packages/core-data/tsconfig.json
@@ -19,6 +19,7 @@
{ "path": "../is-shallow-equal" },
{ "path": "../private-apis" },
{ "path": "../sync" },
+ { "path": "../undo-manager" },
{ "path": "../url" }
],
"include": [ "src/**/*" ]
diff --git a/packages/dependency-extraction-webpack-plugin/lib/util.js b/packages/dependency-extraction-webpack-plugin/lib/util.js
index a22837af6a72e9..ac637eb33fbb2f 100644
--- a/packages/dependency-extraction-webpack-plugin/lib/util.js
+++ b/packages/dependency-extraction-webpack-plugin/lib/util.js
@@ -1,5 +1,9 @@
const WORDPRESS_NAMESPACE = '@wordpress/';
-const BUNDLED_PACKAGES = [ '@wordpress/icons', '@wordpress/interface' ];
+const BUNDLED_PACKAGES = [
+ '@wordpress/icons',
+ '@wordpress/interface',
+ '@wordpress/undo-manager',
+];
/**
* Default request to global transformation
diff --git a/packages/undo-manager/.npmrc b/packages/undo-manager/.npmrc
new file mode 100644
index 00000000000000..43c97e719a5a82
--- /dev/null
+++ b/packages/undo-manager/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/packages/undo-manager/CHANGELOG.md b/packages/undo-manager/CHANGELOG.md
new file mode 100644
index 00000000000000..32b01e3da3a957
--- /dev/null
+++ b/packages/undo-manager/CHANGELOG.md
@@ -0,0 +1,4 @@
+
+
+## Unreleased
+
diff --git a/packages/undo-manager/README.md b/packages/undo-manager/README.md
new file mode 100644
index 00000000000000..cc9727469bcadf
--- /dev/null
+++ b/packages/undo-manager/README.md
@@ -0,0 +1,33 @@
+# Undo Manager
+
+A simple undo manager.
+
+## Installation
+
+Install the module
+
+```bash
+npm install @wordpress/undo-manager --save
+```
+
+## API
+
+
+
+### createUndoManager
+
+Creates an undo manager.
+
+_Returns_
+
+- `UndoManager`: Undo manager.
+
+
+
+## Contributing to this package
+
+This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.
+
+To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md).
+
+
diff --git a/packages/undo-manager/package.json b/packages/undo-manager/package.json
new file mode 100644
index 00000000000000..7ca465023b5e83
--- /dev/null
+++ b/packages/undo-manager/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "@wordpress/undo-manager",
+ "version": "0.1.0",
+ "description": "A small package to manage undo/redo.",
+ "author": "The WordPress Contributors",
+ "license": "GPL-2.0-or-later",
+ "keywords": [
+ "wordpress",
+ "gutenberg",
+ "undo",
+ "history"
+ ],
+ "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/undo-manager/README.md",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/WordPress/gutenberg.git",
+ "directory": "packages/undo-manager"
+ },
+ "bugs": {
+ "url": "https://github.com/WordPress/gutenberg/issues"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "main": "build/index.js",
+ "module": "build-module/index.js",
+ "react-native": "src/index",
+ "types": "build-types",
+ "sideEffects": false,
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "@wordpress/is-shallow-equal": "file:../is-shallow-equal"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/undo-manager/src/index.js b/packages/undo-manager/src/index.js
new file mode 100644
index 00000000000000..379172943dfb02
--- /dev/null
+++ b/packages/undo-manager/src/index.js
@@ -0,0 +1,171 @@
+/**
+ * WordPress dependencies
+ */
+import isShallowEqual from '@wordpress/is-shallow-equal';
+
+/** @typedef {import('./types').HistoryRecord} HistoryRecord */
+/** @typedef {import('./types').HistoryChange} HistoryChange */
+/** @typedef {import('./types').HistoryChanges} HistoryChanges */
+/** @typedef {import('./types').UndoManager} UndoManager */
+
+/**
+ * Merge changes for a single item into a record of changes.
+ *
+ * @param {Record< string, HistoryChange >} changes1 Previous changes
+ * @param {Record< string, HistoryChange >} changes2 NextChanges
+ *
+ * @return {Record< string, HistoryChange >} Merged changes
+ */
+function mergeHistoryChanges( changes1, changes2 ) {
+ /**
+ * @type {Record< string, HistoryChange >}
+ */
+ const newChanges = { ...changes1 };
+ Object.entries( changes2 ).forEach( ( [ key, value ] ) => {
+ if ( newChanges[ key ] ) {
+ newChanges[ key ] = { ...newChanges[ key ], to: value.to };
+ } else {
+ newChanges[ key ] = value;
+ }
+ } );
+
+ return newChanges;
+}
+
+/**
+ * Adds history changes for a single item into a record of changes.
+ *
+ * @param {HistoryRecord} record The record to merge into.
+ * @param {HistoryChanges} changes The changes to merge.
+ */
+const addHistoryChangesIntoRecord = ( record, changes ) => {
+ const existingChangesIndex = record?.findIndex(
+ ( { id: recordIdentifier } ) => {
+ return typeof recordIdentifier === 'string'
+ ? recordIdentifier === changes.id
+ : isShallowEqual( recordIdentifier, changes.id );
+ }
+ );
+ const nextRecord = [ ...record ];
+
+ if ( existingChangesIndex !== -1 ) {
+ // If the edit is already in the stack leave the initial "from" value.
+ nextRecord[ existingChangesIndex ] = {
+ id: changes.id,
+ changes: mergeHistoryChanges(
+ nextRecord[ existingChangesIndex ].changes,
+ changes.changes
+ ),
+ };
+ } else {
+ nextRecord.push( changes );
+ }
+ return nextRecord;
+};
+
+/**
+ * Creates an undo manager.
+ *
+ * @return {UndoManager} Undo manager.
+ */
+export function createUndoManager() {
+ /**
+ * @type {HistoryRecord[]}
+ */
+ let history = [];
+ /**
+ * @type {HistoryRecord}
+ */
+ let stagedRecord = [];
+ /**
+ * @type {number}
+ */
+ let offset = 0;
+
+ const dropPendingRedos = () => {
+ history = history.slice( 0, offset || undefined );
+ offset = 0;
+ };
+
+ const appendStagedRecordToLatestHistoryRecord = () => {
+ const index = history.length === 0 ? 0 : history.length - 1;
+ let latestRecord = history[ index ] ?? [];
+ stagedRecord.forEach( ( changes ) => {
+ latestRecord = addHistoryChangesIntoRecord( latestRecord, changes );
+ } );
+ stagedRecord = [];
+ history[ index ] = latestRecord;
+ };
+
+ /**
+ * Checks whether a record is empty.
+ * A record is considered empty if it the changes keep the same values.
+ * Also updates to function values are ignored.
+ *
+ * @param {HistoryRecord} record
+ * @return {boolean} Whether the record is empty.
+ */
+ const isRecordEmpty = ( record ) => {
+ const filteredRecord = record.filter( ( { changes } ) => {
+ return Object.values( changes ).some(
+ ( { from, to } ) =>
+ typeof from !== 'function' &&
+ typeof to !== 'function' &&
+ ! isShallowEqual( from, to )
+ );
+ } );
+ return ! filteredRecord.length;
+ };
+
+ return {
+ /**
+ * Record changes into the history.
+ *
+ * @param {HistoryRecord=} record A record of changes to record.
+ * @param {boolean} isStaged Whether to immediately create an undo point or not.
+ */
+ addRecord( record, isStaged = false ) {
+ const isEmpty = ! record || isRecordEmpty( record );
+ if ( isStaged ) {
+ if ( isEmpty ) {
+ return;
+ }
+ record.forEach( ( changes ) => {
+ stagedRecord = addHistoryChangesIntoRecord(
+ stagedRecord,
+ changes
+ );
+ } );
+ } else {
+ dropPendingRedos();
+ if ( stagedRecord.length ) {
+ appendStagedRecordToLatestHistoryRecord();
+ }
+ if ( isEmpty ) {
+ return;
+ }
+ history.push( record );
+ }
+ },
+
+ undo() {
+ if ( stagedRecord.length ) {
+ dropPendingRedos();
+ appendStagedRecordToLatestHistoryRecord();
+ }
+ offset -= 1;
+ },
+
+ redo() {
+ offset += 1;
+ },
+
+ getUndoRecord() {
+ return history[ history.length - 1 + offset ];
+ },
+
+ getRedoRecord() {
+ return history[ history.length + offset ];
+ },
+ };
+}
diff --git a/packages/undo-manager/src/test/index.js b/packages/undo-manager/src/test/index.js
new file mode 100644
index 00000000000000..32ec2713f7bc28
--- /dev/null
+++ b/packages/undo-manager/src/test/index.js
@@ -0,0 +1,257 @@
+/**
+ * Internal dependencies
+ */
+import { createUndoManager } from '../';
+
+describe( 'Undo Manager', () => {
+ it( 'stacks undo levels', () => {
+ const undo = createUndoManager();
+
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: undefined, to: 1 } } },
+ ] );
+ expect( undo.getUndoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: undefined, to: 1 } } },
+ ] );
+
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 1, to: 2 } } },
+ ] );
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 2, to: 3 } } },
+ ] );
+ expect( undo.getUndoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: 2, to: 3 } } },
+ ] );
+ } );
+
+ it( 'handles undos/redos', () => {
+ const undo = createUndoManager();
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: undefined, to: 1 } } },
+ ] );
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 1, to: 2 } } },
+ ] );
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 2, to: 3 } } },
+ ] );
+
+ undo.undo();
+ expect( undo.getUndoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: 1, to: 2 } } },
+ ] );
+ expect( undo.getRedoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: 2, to: 3 } } },
+ ] );
+
+ undo.undo();
+ expect( undo.getUndoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: undefined, to: 1 } } },
+ ] );
+ expect( undo.getRedoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: 1, to: 2 } } },
+ ] );
+
+ undo.redo();
+ undo.redo();
+ expect( undo.getUndoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: 2, to: 3 } } },
+ ] );
+
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 3, to: 4 } } },
+ ] );
+ expect( undo.getUndoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: 3, to: 4 } } },
+ ] );
+
+ // Check that undoing and editing will slice of
+ // all the levels after the current one.
+ undo.undo();
+ undo.undo();
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 2, to: 5 } } },
+ ] );
+ undo.undo();
+ expect( undo.getUndoRecord() ).toEqual( [
+ { id: '1', changes: { value: { from: 1, to: 2 } } },
+ ] );
+ } );
+
+ it( 'handles staged edits', () => {
+ const undo = createUndoManager();
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: undefined, to: 1 } } },
+ ] );
+ undo.addRecord(
+ [ { id: '1', changes: { value2: { from: undefined, to: 2 } } } ],
+ true
+ );
+ undo.addRecord(
+ [ { id: '1', changes: { value: { from: 1, to: 3 } } } ],
+ true
+ );
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 3, to: 4 } } },
+ ] );
+ undo.undo();
+ expect( undo.getUndoRecord() ).toEqual( [
+ {
+ id: '1',
+ changes: {
+ value: { from: undefined, to: 3 },
+ value2: { from: undefined, to: 2 },
+ },
+ },
+ ] );
+ } );
+
+ it( 'handles explicit undo level creation', () => {
+ const undo = createUndoManager();
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: undefined, to: 1 } } },
+ ] );
+ // These three calls do nothing because they're empty.
+ undo.addRecord( [] );
+ undo.addRecord();
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: 1, to: 1 } } },
+ ] );
+ // Check that nothing happens if there are no pending
+ // transient edits.
+ undo.undo();
+ expect( undo.getUndoRecord() ).toBe( undefined );
+ undo.redo();
+
+ // Check that transient edits are merged into the last
+ // edits.
+ undo.addRecord(
+ [ { id: '1', changes: { value2: { from: undefined, to: 2 } } } ],
+ true
+ );
+ undo.addRecord( [] ); // Records the staged edits.
+ undo.undo();
+ expect( undo.getRedoRecord() ).toEqual( [
+ {
+ id: '1',
+ changes: {
+ value: { from: undefined, to: 1 },
+ value2: { from: undefined, to: 2 },
+ },
+ },
+ ] );
+ } );
+
+ it( 'explicitly creates an undo level when undoing while there are pending transient edits', () => {
+ const undo = createUndoManager();
+ undo.addRecord( [
+ { id: '1', changes: { value: { from: undefined, to: 1 } } },
+ ] );
+ undo.addRecord(
+ [ { id: '1', changes: { value2: { from: undefined, to: 2 } } } ],
+ true
+ );
+ undo.undo();
+ expect( undo.getRedoRecord() ).toEqual( [
+ {
+ id: '1',
+ changes: {
+ value: { from: undefined, to: 1 },
+ value2: { from: undefined, to: 2 },
+ },
+ },
+ ] );
+ } );
+
+ it( 'supports records as ids', () => {
+ const undo = createUndoManager();
+
+ undo.addRecord(
+ [
+ {
+ id: { kind: 'postType', name: 'post', recordId: 1 },
+ changes: { value: { from: undefined, to: 1 } },
+ },
+ ],
+ true
+ );
+ undo.addRecord(
+ [
+ {
+ id: { kind: 'postType', name: 'post', recordId: 1 },
+ changes: { value2: { from: undefined, to: 2 } },
+ },
+ ],
+ true
+ );
+ undo.addRecord(
+ [
+ {
+ id: { kind: 'postType', name: 'post', recordId: 2 },
+ changes: { value: { from: undefined, to: 3 } },
+ },
+ ],
+ true
+ );
+ undo.addRecord();
+ expect( undo.getUndoRecord() ).toEqual( [
+ {
+ id: { kind: 'postType', name: 'post', recordId: 1 },
+ changes: {
+ value: { from: undefined, to: 1 },
+ value2: { from: undefined, to: 2 },
+ },
+ },
+ {
+ id: { kind: 'postType', name: 'post', recordId: 2 },
+ changes: {
+ value: { from: undefined, to: 3 },
+ },
+ },
+ ] );
+ } );
+
+ it( 'should ignore empty records', () => {
+ const undo = createUndoManager();
+
+ // All the following changes are considered empty for different reasons.
+ undo.addRecord();
+ undo.addRecord( [] );
+ undo.addRecord( [
+ { id: '1', changes: { a: { from: 'value', to: 'value' } } },
+ ] );
+ undo.addRecord( [
+ {
+ id: '1',
+ changes: {
+ a: { from: 'value', to: 'value' },
+ b: { from: () => {}, to: () => {} },
+ },
+ },
+ ] );
+
+ expect( undo.getUndoRecord() ).toBeUndefined();
+
+ // The following changes is not empty
+ // and should also record the function changes in the history.
+
+ undo.addRecord( [
+ {
+ id: '1',
+ changes: {
+ a: { from: 'value1', to: 'value2' },
+ b: { from: () => {}, to: () => {} },
+ },
+ },
+ ] );
+
+ const undoRecord = undo.getUndoRecord();
+ expect( undoRecord ).not.toBeUndefined();
+ // b is included in the changes.
+ expect( Object.keys( undoRecord[ 0 ].changes ) ).toEqual( [
+ 'a',
+ 'b',
+ ] );
+ } );
+} );
diff --git a/packages/undo-manager/src/types.ts b/packages/undo-manager/src/types.ts
new file mode 100644
index 00000000000000..e2e1d995f8e5db
--- /dev/null
+++ b/packages/undo-manager/src/types.ts
@@ -0,0 +1,19 @@
+export type HistoryChange = {
+ from: any;
+ to: any;
+};
+
+export type HistoryChanges = {
+ id: string | Record< string, any >;
+ changes: Record< string, HistoryChange >;
+};
+
+export type HistoryRecord = Array< HistoryChanges >;
+
+export type UndoManager = {
+ addRecord: ( record: HistoryRecord, isStaged: boolean ) => void;
+ undo: () => void;
+ redo: () => void;
+ getUndoRecord: () => HistoryRecord;
+ getRedoRecord: () => HistoryRecord;
+};
diff --git a/packages/undo-manager/tsconfig.json b/packages/undo-manager/tsconfig.json
new file mode 100644
index 00000000000000..e53ddb4792e576
--- /dev/null
+++ b/packages/undo-manager/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "declarationDir": "build-types",
+ "types": [ "node" ]
+ },
+ "references": [ { "path": "../is-shallow-equal" } ],
+ "include": [ "src/**/*" ]
+}
diff --git a/tools/webpack/packages.js b/tools/webpack/packages.js
index e5bb74abdb0a1f..3dc8407d7974b9 100644
--- a/tools/webpack/packages.js
+++ b/tools/webpack/packages.js
@@ -24,7 +24,11 @@ const WORDPRESS_NAMESPACE = '@wordpress/';
// Experimental or other packages that should be private are bundled when used.
// That way, we can iterate on these package without making them part of the public API.
// See: https://github.com/WordPress/gutenberg/pull/19809
-const BUNDLED_PACKAGES = [ '@wordpress/icons', '@wordpress/interface' ];
+const BUNDLED_PACKAGES = [
+ '@wordpress/icons',
+ '@wordpress/interface',
+ '@wordpress/undo-manager',
+];
// PHP files in packages that have to be copied during build.
const bundledPackagesPhpConfig = [
diff --git a/tsconfig.json b/tsconfig.json
index 2c395450fb6a0e..4ee1787a247cf7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -44,6 +44,7 @@
{ "path": "packages/style-engine" },
{ "path": "packages/sync" },
{ "path": "packages/token-list" },
+ { "path": "packages/undo-manager" },
{ "path": "packages/url" },
{ "path": "packages/warning" },
{ "path": "packages/wordcount" }
From 5c74b3dc7f77f24985196a381c8a0c2e0409a76a Mon Sep 17 00:00:00 2001
From: Andrei Draganescu
Date: Mon, 11 Sep 2023 14:49:38 +0300
Subject: [PATCH 04/42] adds distraction free mode off animation state to post
editor header (#54244)
---
packages/edit-post/src/components/header/index.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js
index ab4bbd4bbc5d15..4d567fe37493d5 100644
--- a/packages/edit-post/src/components/header/index.js
+++ b/packages/edit-post/src/components/header/index.js
@@ -22,11 +22,13 @@ import DocumentActions from './document-actions';
const slideY = {
hidden: { y: '-50px' },
+ distractionFreeInactive: { y: 0 },
hover: { y: 0, transition: { type: 'tween', delay: 0.2 } },
};
const slideX = {
hidden: { x: '-100%' },
+ distractionFreeInactive: { x: 0 },
hover: { x: 0, transition: { type: 'tween', delay: 0.2 } },
};
From 3f65c725c7ec2b8b64607f27b21f19dce9e644bb Mon Sep 17 00:00:00 2001
From: Andrei Draganescu
Date: Mon, 11 Sep 2023 14:50:27 +0300
Subject: [PATCH 05/42] Make order of pinned items consistent (#53908)
* move the editor pinned items last in post editor
* try identifying pinned items by aria controls attribute
* update snapshots with the new attrs
* remove snapshot as on trunk
* update snapshots
* undo unrelated snapshot update
---
.../editor/plugins/__snapshots__/plugins-api.test.js.snap | 4 ++--
packages/edit-post/src/components/layout/index.js | 3 ++-
.../src/components/complementary-area-toggle/index.js | 1 +
.../interface/src/components/complementary-area/index.js | 7 +++++--
packages/interface/src/components/pinned-items/style.scss | 7 +++++--
5 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap
index 4cac5cd6893cda..9e4a5ac3ad6f61 100644
--- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap
+++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap
@@ -2,6 +2,6 @@
exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `"My Custom Panel"`;
-exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `""`;
+exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `""`;
-exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `""`;
+exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `""`;
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index c0018d40d6ef82..870a3e9044dfcb 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -266,7 +266,7 @@ function Layout() {
-
+
+
>
);
}
diff --git a/packages/interface/src/components/complementary-area-toggle/index.js b/packages/interface/src/components/complementary-area-toggle/index.js
index 1abdeb2c84f260..b6690b7df5fc5d 100644
--- a/packages/interface/src/components/complementary-area-toggle/index.js
+++ b/packages/interface/src/components/complementary-area-toggle/index.js
@@ -31,6 +31,7 @@ function ComplementaryAreaToggle( {
return (
{
if ( isSelected ) {
disableComplementaryArea( scope );
diff --git a/packages/interface/src/components/complementary-area/index.js b/packages/interface/src/components/complementary-area/index.js
index de69762b6a15c4..887c447d9291e4 100644
--- a/packages/interface/src/components/complementary-area/index.js
+++ b/packages/interface/src/components/complementary-area/index.js
@@ -27,10 +27,12 @@ function ComplementaryAreaSlot( { scope, ...props } ) {
return ;
}
-function ComplementaryAreaFill( { scope, children, className } ) {
+function ComplementaryAreaFill( { scope, children, className, id } ) {
return (
- { children }
+
+ { children }
+
);
}
@@ -200,6 +202,7 @@ function ComplementaryArea( {
className
) }
scope={ scope }
+ id={ identifier.replace( '/', ':' ) }
>
Date: Mon, 11 Sep 2023 21:59:47 +1000
Subject: [PATCH 06/42] [RNMobile] Fix issue with missing characters in Add
Media placeholder button (#54281)
* Fix media placeholder text issue
* Update CHANGELOG
* Update Gallery block media placeholder text
* Update letter case for File block button
---
.../src/components/media-placeholder/index.native.js | 11 +++++------
packages/block-library/src/file/edit.native.js | 2 +-
packages/block-library/src/gallery/edit.js | 2 +-
packages/react-native-editor/CHANGELOG.md | 1 +
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js
index 938c01e92b4ed5..e597b65d63b865 100644
--- a/packages/block-editor/src/components/media-placeholder/index.native.js
+++ b/packages/block-editor/src/components/media-placeholder/index.native.js
@@ -2,7 +2,6 @@
* External dependencies
*/
import { View, Text, TouchableOpacity } from 'react-native';
-import { sentenceCase } from 'change-case';
/**
* WordPress dependencies
@@ -104,13 +103,13 @@ function MediaPlaceholder( props ) {
let instructions = labels.instructions;
if ( instructions === undefined ) {
if ( isImage ) {
- instructions = __( 'ADD IMAGE' );
+ instructions = __( 'Add image' );
} else if ( isVideo ) {
- instructions = __( 'ADD VIDEO' );
+ instructions = __( 'Add video' );
} else if ( isAudio ) {
- instructions = __( 'ADD AUDIO' );
+ instructions = __( 'Add audio' );
} else {
- instructions = __( 'ADD IMAGE OR VIDEO' );
+ instructions = __( 'Add image or video' );
}
}
@@ -171,7 +170,7 @@ function MediaPlaceholder( props ) {
onPress={ onButtonPress( open ) }
>
- { sentenceCase( instructions ) }
+ { instructions }
>
diff --git a/packages/block-library/src/file/edit.native.js b/packages/block-library/src/file/edit.native.js
index 3266adf26098de..c4217c60263069 100644
--- a/packages/block-library/src/file/edit.native.js
+++ b/packages/block-library/src/file/edit.native.js
@@ -552,7 +552,7 @@ export class FileEdit extends Component {
icon={ }
labels={ {
title: __( 'File' ),
- instructions: __( 'CHOOSE A FILE' ),
+ instructions: __( 'Choose a file' ),
} }
onSelect={ this.onSelectFile }
onFocus={ this.props.onFocus }
diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js
index 70e006560642e6..68ada058abefa8 100644
--- a/packages/block-library/src/gallery/edit.js
+++ b/packages/block-library/src/gallery/edit.js
@@ -75,7 +75,7 @@ const ALLOWED_MEDIA_TYPES = [ 'image' ];
const allowedBlocks = [ 'core/image' ];
const PLACEHOLDER_TEXT = Platform.isNative
- ? __( 'ADD MEDIA' )
+ ? __( 'Add media' )
: __( 'Drag images, upload new ones or select files from your library.' );
const MOBILE_CONTROL_PROPS_RANGE_CONTROL = Platform.isNative
diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md
index f95aba8b9d4d6e..031c77b8ee8ed7 100644
--- a/packages/react-native-editor/CHANGELOG.md
+++ b/packages/react-native-editor/CHANGELOG.md
@@ -11,6 +11,7 @@ For each user feature we should also add a importance categorization label to i
## Unreleased
- [*] Fix the obscurred "Insert from URL" input for media blocks when using a device in landscape orientation. [#54096]
+- [*] Fix issue with missing characters in Add Media placeholder button [#54281]
## 1.103.1
- [**] Fix long-press gestures not working in RichText component [Android] [#54213]
From fee14675244a818e1a9e6e5bd0c5824d7d225ec2 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 11 Sep 2023 16:10:14 +0300
Subject: [PATCH 07/42] RichText: replace deprecated multiline prop with simple
multiple instances (#54310)
---
.../block-api/block-attributes.md | 33 ---
packages/block-editor/CHANGELOG.md | 4 +
.../src/components/rich-text/index.js | 87 ++++----
.../src/components/rich-text/index.native.js | 56 ++---
.../src/components/rich-text/multiline.js | 121 +++++++++++
.../src/components/rich-text/split-value.js | 24 +--
.../src/components/rich-text/use-enter.js | 53 ++---
.../components/rich-text/use-paste-handler.js | 35 +---
packages/block-editor/src/store/utils.js | 13 +-
.../block-library/src/pullquote/deprecated.js | 21 +-
packages/editor/README.md | 5 -
packages/rich-text/README.md | 7 +-
packages/rich-text/src/component/index.js | 15 +-
.../rich-text/src/component/index.native.js | 23 +-
.../src/component/use-copy-handler.js | 4 +-
.../rich-text/src/component/use-delete.js | 28 +--
packages/rich-text/src/create.js | 147 +------------
packages/rich-text/src/get-text-content.js | 14 +-
packages/rich-text/src/index.ts | 4 +-
.../rich-text/src/insert-line-separator.js | 43 ----
packages/rich-text/src/is-empty.js | 36 ----
.../rich-text/src/remove-line-separator.js | 55 -----
packages/rich-text/src/special-characters.js | 5 -
packages/rich-text/src/split.js | 8 +-
.../src/test/__snapshots__/to-dom.js.snap | 124 -----------
packages/rich-text/src/test/create.js | 51 ++---
packages/rich-text/src/test/helpers/index.js | 196 ------------------
.../src/test/insert-line-separator.js | 104 ----------
packages/rich-text/src/test/is-empty.js | 61 +-----
packages/rich-text/src/test/split.js | 33 ---
packages/rich-text/src/test/to-dom.js | 21 +-
packages/rich-text/src/test/to-html-string.js | 15 --
packages/rich-text/src/to-dom.js | 8 +-
packages/rich-text/src/to-html-string.js | 7 +-
packages/rich-text/src/to-tree.js | 85 +-------
schemas/json/block.json | 4 -
.../rich-text-deprecated-multiline.spec.js | 126 +++++++++++
37 files changed, 402 insertions(+), 1274 deletions(-)
create mode 100644 packages/block-editor/src/components/rich-text/multiline.js
delete mode 100644 packages/rich-text/src/insert-line-separator.js
delete mode 100644 packages/rich-text/src/remove-line-separator.js
delete mode 100644 packages/rich-text/src/test/insert-line-separator.js
create mode 100644 test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js
diff --git a/docs/reference-guides/block-api/block-attributes.md b/docs/reference-guides/block-api/block-attributes.md
index eafc73c79938f3..0fbbeeb13680e6 100644
--- a/docs/reference-guides/block-api/block-attributes.md
+++ b/docs/reference-guides/block-api/block-attributes.md
@@ -305,39 +305,6 @@ Attribute available in the block:
{ "content": "The inner text of the figcaption element" }
```
-Use the `multiline` property to extract the inner HTML of matching tag names for the use in `RichText` with the `multiline` prop.
-
-_Example_: Extract the `content` attribute from a blockquote element found in the block's markup.
-
-Saved content:
-```html
-
- Block Content
-
-
- First line
- Second line
-
-
-```
-
-Attribute definition:
-```js
-{
- content: {
- type: 'string',
- source: 'html',
- multiline: 'p',
- selector: 'blockquote',
- }
-}
-```
-
-Attribute available in the block:
-```js
-{ "content": "First line
Second line
" }
-```
-
### `query` source
Use `query` to extract an array of values from markup. Entries of the array are determined by the `selector` argument, where each matched element within the block will have an entry structured corresponding to the second argument, an object of attribute sources.
diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md
index 63a6281f78b426..5441536e1ab112 100644
--- a/packages/block-editor/CHANGELOG.md
+++ b/packages/block-editor/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+- The Deprecated multiline prop on RichText will now fall back to using multiple
+ rich text instances instead of a single multiline instance. The prop remains
+ deprecated.
+
## 12.9.0 (2023-08-31)
### Enhancements
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index a22b251dd607c2..aa6a44461ee73c 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -44,8 +44,9 @@ import { useInputEvents } from './use-input-events';
import { useInsertReplacementText } from './use-insert-replacement-text';
import { useFirefoxCompat } from './use-firefox-compat';
import FormatEdit from './format-edit';
-import { getMultilineTag, getAllowedFormats } from './utils';
+import { getAllowedFormats } from './utils';
import { Content } from './content';
+import RichTextMultiline from './multiline';
export const keyboardShortcutContext = createContext();
export const inputEventContext = createContext();
@@ -81,12 +82,12 @@ function removeNativeProps( props ) {
return restProps;
}
-function RichTextWrapper(
+export function RichTextWrapper(
{
children,
tagName = 'div',
- value: originalValue = '',
- onChange: originalOnChange,
+ value: adjustedValue = '',
+ onChange: adjustedOnChange,
isSelected: originalIsSelected,
multiline,
inlineToolbar,
@@ -111,18 +112,6 @@ function RichTextWrapper(
},
forwardedRef
) {
- if ( multiline ) {
- deprecated( 'wp.blockEditor.RichText multiline prop', {
- since: '6.1',
- version: '6.3',
- alternative: 'nested blocks (InnerBlocks)',
- link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/',
- } );
- }
-
- const instanceId = useInstanceId( RichTextWrapper );
-
- identifier = identifier || instanceId;
props = removeNativeProps( props );
const anchorRef = useRef();
@@ -157,33 +146,12 @@ function RichTextWrapper(
const { getSelectionStart, getSelectionEnd, getBlockRootClientId } =
useSelect( blockEditorStore );
const { selectionChange } = useDispatch( blockEditorStore );
- const multilineTag = getMultilineTag( multiline );
const adjustedAllowedFormats = getAllowedFormats( {
allowedFormats,
disableFormats,
} );
const hasFormats =
! adjustedAllowedFormats || adjustedAllowedFormats.length > 0;
- let adjustedValue = originalValue;
- let adjustedOnChange = originalOnChange;
-
- // Handle deprecated format.
- if ( Array.isArray( originalValue ) ) {
- deprecated( 'wp.blockEditor.RichText value prop as children type', {
- since: '6.1',
- version: '6.3',
- alternative: 'value prop as string',
- link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
- } );
-
- adjustedValue = childrenSource.toHTML( originalValue );
- adjustedOnChange = ( newValue ) =>
- originalOnChange(
- childrenSource.fromDOM(
- __unstableCreateElement( document, newValue ).childNodes
- )
- );
- }
const onSelectionChange = useCallback(
( start, end ) => {
@@ -292,7 +260,6 @@ function RichTextWrapper(
onSelectionChange,
placeholder,
__unstableIsSelected: isSelected,
- __unstableMultilineTag: multilineTag,
__unstableDisableFormats: disableFormats,
preserveWhiteSpace,
__unstableDependencies: [ ...dependencies, tagName ],
@@ -380,7 +347,6 @@ function RichTextWrapper(
onReplace,
onSplit,
__unstableEmbedURLOnPaste,
- multilineTag,
preserveWhiteSpace,
pastePlainText,
} ),
@@ -394,7 +360,6 @@ function RichTextWrapper(
value,
onReplace,
onSplit,
- multilineTag,
onChange,
disableLineBreaks,
onSplitAtEnd,
@@ -421,7 +386,47 @@ function RichTextWrapper(
);
}
-const ForwardedRichTextContainer = forwardRef( RichTextWrapper );
+const ForwardedRichTextWrapper = forwardRef( RichTextWrapper );
+
+function RichTextSwitcher( props, ref ) {
+ let value = props.value;
+ let onChange = props.onChange;
+
+ // Handle deprecated format.
+ if ( Array.isArray( value ) ) {
+ deprecated( 'wp.blockEditor.RichText value prop as children type', {
+ since: '6.1',
+ version: '6.3',
+ alternative: 'value prop as string',
+ link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/',
+ } );
+
+ value = childrenSource.toHTML( props.value );
+ onChange = ( newValue ) =>
+ props.onChange(
+ childrenSource.fromDOM(
+ __unstableCreateElement( document, newValue ).childNodes
+ )
+ );
+ }
+
+ const Component = props.multiline
+ ? RichTextMultiline
+ : ForwardedRichTextWrapper;
+ const instanceId = useInstanceId( RichTextSwitcher );
+
+ return (
+
+ );
+}
+
+const ForwardedRichTextContainer = forwardRef( RichTextSwitcher );
ForwardedRichTextContainer.Content = Content;
ForwardedRichTextContainer.isEmpty = ( value ) => {
diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js
index b0c82848db6876..67f41f9ae8c108 100644
--- a/packages/block-editor/src/components/rich-text/index.native.js
+++ b/packages/block-editor/src/components/rich-text/index.native.js
@@ -20,13 +20,9 @@ import {
__experimentalRichText as RichText,
__unstableCreateElement,
isEmpty,
- __unstableIsEmptyLine as isEmptyLine,
insert,
- __unstableInsertLineSeparator as insertLineSeparator,
create,
- replace,
split,
- __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR,
toHTMLString,
slice,
} from '@wordpress/rich-text';
@@ -338,32 +334,20 @@ function RichTextWrapper(
onCustomEnter();
}
- if ( multiline ) {
- if ( shiftKey ) {
- if ( ! disableLineBreaks ) {
- onChange( insert( value, '\n' ) );
- }
- } else if ( canSplit && isEmptyLine( value ) ) {
- splitValue( value );
- } else {
- onChange( insertLineSeparator( value ) );
- }
- } else {
- const { text, start: splitStart, end: splitEnd } = value;
- const canSplitAtEnd =
- onSplitAtEnd &&
- splitStart === splitEnd &&
- splitEnd === text.length;
-
- if ( shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) {
- if ( ! disableLineBreaks ) {
- onChange( insert( value, '\n' ) );
- }
- } else if ( ! canSplit && canSplitAtEnd ) {
- onSplitAtEnd();
- } else if ( canSplit ) {
- splitValue( value );
+ const { text, start: splitStart, end: splitEnd } = value;
+ const canSplitAtEnd =
+ onSplitAtEnd &&
+ splitStart === splitEnd &&
+ splitEnd === text.length;
+
+ if ( shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) {
+ if ( ! disableLineBreaks ) {
+ onChange( insert( value, '\n' ) );
}
+ } else if ( ! canSplit && canSplitAtEnd ) {
+ onSplitAtEnd();
+ } else if ( canSplit ) {
+ splitValue( value );
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -471,20 +455,8 @@ function RichTextWrapper(
} );
if ( typeof content === 'string' ) {
- let valueToInsert = create( { html: content } );
-
+ const valueToInsert = create( { html: content } );
addActiveFormats( valueToInsert, activeFormats );
-
- // If the content should be multiline, we should process text
- // separated by a line break as separate lines.
- if ( multilineTag ) {
- valueToInsert = replace(
- valueToInsert,
- /\n+/g,
- LINE_SEPARATOR
- );
- }
-
onChange( insert( value, valueToInsert ) );
} else if ( content.length > 0 ) {
// When an URL is pasted in an empty paragraph then the EmbedHandlerPicker should showcase options allowing the transformation of that URL
diff --git a/packages/block-editor/src/components/rich-text/multiline.js b/packages/block-editor/src/components/rich-text/multiline.js
new file mode 100644
index 00000000000000..760a7718fd864a
--- /dev/null
+++ b/packages/block-editor/src/components/rich-text/multiline.js
@@ -0,0 +1,121 @@
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+import deprecated from '@wordpress/deprecated';
+import { useDispatch } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { RichTextWrapper } from './';
+import { store as blockEditorStore } from '../../store';
+import { useBlockEditContext } from '../block-edit';
+import { getMultilineTag } from './utils';
+
+function RichTextMultiline(
+ {
+ children,
+ identifier,
+ tagName: TagName = 'div',
+ value = '',
+ onChange,
+ multiline,
+ ...props
+ },
+ forwardedRef
+) {
+ deprecated( 'wp.blockEditor.RichText multiline prop', {
+ since: '6.1',
+ version: '6.3',
+ alternative: 'nested blocks (InnerBlocks)',
+ link: 'https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/',
+ } );
+
+ const { clientId } = useBlockEditContext();
+ const { selectionChange } = useDispatch( blockEditorStore );
+
+ const multilineTagName = getMultilineTag( multiline );
+ value = value || `<${ multilineTagName }>${ multilineTagName }>`;
+ const padded = `${ multilineTagName }>${ value }<${ multilineTagName }>`;
+ const values = padded.split(
+ `${ multilineTagName }><${ multilineTagName }>`
+ );
+
+ values.shift();
+ values.pop();
+
+ function _onChange( newValues ) {
+ onChange(
+ `<${ multilineTagName }>${ newValues.join(
+ `${ multilineTagName }><${ multilineTagName }>`
+ ) }${ multilineTagName }>`
+ );
+ }
+
+ return (
+
+ { values.map( ( _value, index ) => {
+ return (
+ {
+ const newValues = values.slice();
+ newValues[ index ] = newValue;
+ _onChange( newValues );
+ } }
+ isSelected={ undefined }
+ onSplit={ ( v ) => v }
+ onReplace={ ( array ) => {
+ const newValues = values.slice();
+ newValues.splice( index, 1, ...array );
+ _onChange( newValues );
+ selectionChange(
+ clientId,
+ `${ identifier }-${ index + 1 }`,
+ 0,
+ 0
+ );
+ } }
+ onMerge={ ( forward ) => {
+ const newValues = values.slice();
+ let offset = 0;
+ if ( forward ) {
+ if ( ! newValues[ index + 1 ] ) return;
+ newValues.splice(
+ index,
+ 2,
+ newValues[ index ] + newValues[ index + 1 ]
+ );
+ offset = newValues[ index ].length - 1;
+ } else {
+ if ( ! newValues[ index - 1 ] ) return;
+ newValues.splice(
+ index - 1,
+ 2,
+ newValues[ index - 1 ] + newValues[ index ]
+ );
+ offset = newValues[ index - 1 ].length - 1;
+ }
+ _onChange( newValues );
+ selectionChange(
+ clientId,
+ `${ identifier }-${
+ index - ( forward ? 0 : 1 )
+ }`,
+ offset,
+ offset
+ );
+ } }
+ { ...props }
+ />
+ );
+ } ) }
+
+ );
+}
+
+export default forwardRef( RichTextMultiline );
diff --git a/packages/block-editor/src/components/rich-text/split-value.js b/packages/block-editor/src/components/rich-text/split-value.js
index 0ec083c9fe1e50..17f54d9c9edd01 100644
--- a/packages/block-editor/src/components/rich-text/split-value.js
+++ b/packages/block-editor/src/components/rich-text/split-value.js
@@ -8,13 +8,7 @@ import { isEmpty, split, toHTMLString } from '@wordpress/rich-text';
* as a result of splitting the block by pressing enter, or with blocks as a
* result of splitting the block by pasting block content in the instance.
*/
-export function splitValue( {
- value,
- pastedBlocks = [],
- onReplace,
- onSplit,
- multilineTag,
-} ) {
+export function splitValue( { value, pastedBlocks = [], onReplace, onSplit } ) {
if ( ! onReplace || ! onSplit ) {
return;
}
@@ -38,13 +32,7 @@ export function splitValue( {
// the enter key.
if ( ! hasPastedBlocks || ! isEmpty( before ) ) {
blocks.push(
- onSplit(
- toHTMLString( {
- value: before,
- multilineTag,
- } ),
- ! isAfterOriginal
- )
+ onSplit( toHTMLString( { value: before } ), ! isAfterOriginal )
);
lastPastedBlockIndex += 1;
}
@@ -60,13 +48,7 @@ export function splitValue( {
// the enter key.
if ( ! hasPastedBlocks || ! isEmpty( after ) ) {
blocks.push(
- onSplit(
- toHTMLString( {
- value: after,
- multilineTag,
- } ),
- isAfterOriginal
- )
+ onSplit( toHTMLString( { value: after } ), isAfterOriginal )
);
}
diff --git a/packages/block-editor/src/components/rich-text/use-enter.js b/packages/block-editor/src/components/rich-text/use-enter.js
index 623203fb687df0..08c5abf9d7b6bc 100644
--- a/packages/block-editor/src/components/rich-text/use-enter.js
+++ b/packages/block-editor/src/components/rich-text/use-enter.js
@@ -4,11 +4,7 @@
import { useRef } from '@wordpress/element';
import { useRefEffect } from '@wordpress/compose';
import { ENTER } from '@wordpress/keycodes';
-import {
- insert,
- __unstableIsEmptyLine as isEmptyLine,
- __unstableInsertLineSeparator as insertLineSeparator,
-} from '@wordpress/rich-text';
+import { insert } from '@wordpress/rich-text';
import { getBlockTransforms, findTransform } from '@wordpress/blocks';
import { useDispatch } from '@wordpress/data';
@@ -37,7 +33,6 @@ export function useEnter( props ) {
value,
onReplace,
onSplit,
- multilineTag,
onChange,
disableLineBreaks,
onSplitAtEnd,
@@ -67,40 +62,22 @@ export function useEnter( props ) {
}
}
- if ( multilineTag ) {
- if ( event.shiftKey ) {
- if ( ! disableLineBreaks ) {
- onChange( insert( _value, '\n' ) );
- }
- } else if ( canSplit && isEmptyLine( _value ) ) {
- splitValue( {
- value: _value,
- onReplace,
- onSplit,
- multilineTag,
- } );
- } else {
- onChange( insertLineSeparator( _value ) );
- }
- } else {
- const { text, start, end } = _value;
- const canSplitAtEnd =
- onSplitAtEnd && start === end && end === text.length;
+ const { text, start, end } = _value;
+ const canSplitAtEnd =
+ onSplitAtEnd && start === end && end === text.length;
- if ( event.shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) {
- if ( ! disableLineBreaks ) {
- onChange( insert( _value, '\n' ) );
- }
- } else if ( ! canSplit && canSplitAtEnd ) {
- onSplitAtEnd();
- } else if ( canSplit ) {
- splitValue( {
- value: _value,
- onReplace,
- onSplit,
- multilineTag,
- } );
+ if ( event.shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) {
+ if ( ! disableLineBreaks ) {
+ onChange( insert( _value, '\n' ) );
}
+ } else if ( ! canSplit && canSplitAtEnd ) {
+ onSplitAtEnd();
+ } else if ( canSplit ) {
+ splitValue( {
+ value: _value,
+ onReplace,
+ onSplit,
+ } );
}
}
diff --git a/packages/block-editor/src/components/rich-text/use-paste-handler.js b/packages/block-editor/src/components/rich-text/use-paste-handler.js
index d64d7ca6b15bb5..6ebd33e507d6c3 100644
--- a/packages/block-editor/src/components/rich-text/use-paste-handler.js
+++ b/packages/block-editor/src/components/rich-text/use-paste-handler.js
@@ -9,13 +9,7 @@ import {
findTransform,
getBlockTransforms,
} from '@wordpress/blocks';
-import {
- isEmpty,
- insert,
- create,
- replace,
- __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR,
-} from '@wordpress/rich-text';
+import { isEmpty, insert, create } from '@wordpress/rich-text';
import { isURL } from '@wordpress/url';
/**
@@ -27,23 +21,6 @@ import { shouldDismissPastedFiles } from '../../utils/pasting';
/** @typedef {import('@wordpress/rich-text').RichTextValue} RichTextValue */
-/**
- * Replaces line separators with line breaks if not multiline.
- * Replaces line breaks with line separators if multiline.
- *
- * @param {RichTextValue} value Value to adjust.
- * @param {boolean} isMultiline Whether to adjust to multiline or not.
- *
- * @return {RichTextValue} Adjusted value.
- */
-function adjustLines( value, isMultiline ) {
- if ( isMultiline ) {
- return replace( value, /\n+/g, LINE_SEPARATOR );
- }
-
- return replace( value, new RegExp( LINE_SEPARATOR, 'g' ), '\n' );
-}
-
export function usePasteHandler( props ) {
const propsRef = useRef( props );
propsRef.current = props;
@@ -59,7 +36,6 @@ export function usePasteHandler( props ) {
onReplace,
onSplit,
__unstableEmbedURLOnPaste,
- multilineTag,
preserveWhiteSpace,
pastePlainText,
} = propsRef.current;
@@ -178,7 +154,6 @@ export function usePasteHandler( props ) {
pastedBlocks: blocks,
onReplace,
onSplit,
- multilineTag,
} );
}
@@ -220,12 +195,7 @@ export function usePasteHandler( props ) {
} );
if ( typeof content === 'string' ) {
- let valueToInsert = create( { html: content } );
-
- // If the content should be multiline, we should process text
- // separated by a line break as separate lines.
- valueToInsert = adjustLines( valueToInsert, !! multilineTag );
-
+ const valueToInsert = create( { html: content } );
addActiveFormats( valueToInsert, value.activeFormats );
onChange( insert( value, valueToInsert ) );
} else if ( content.length > 0 ) {
@@ -237,7 +207,6 @@ export function usePasteHandler( props ) {
pastedBlocks: content,
onReplace,
onSplit,
- multilineTag,
} );
}
}
diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js
index 66749753a9b802..4a19d76d1a4723 100644
--- a/packages/block-editor/src/store/utils.js
+++ b/packages/block-editor/src/store/utils.js
@@ -6,14 +6,7 @@
* @return {Object} The mapped object.
*/
export function mapRichTextSettings( attributeDefinition ) {
- const {
- multiline: multilineTag,
- __unstableMultilineWrapperTags: multilineWrapperTags,
- __unstablePreserveWhiteSpace: preserveWhiteSpace,
- } = attributeDefinition;
- return {
- multilineTag,
- multilineWrapperTags,
- preserveWhiteSpace,
- };
+ const { __unstablePreserveWhiteSpace: preserveWhiteSpace } =
+ attributeDefinition;
+ return { preserveWhiteSpace };
}
diff --git a/packages/block-library/src/pullquote/deprecated.js b/packages/block-library/src/pullquote/deprecated.js
index 46ac8d2f13708b..2839a6d2b88042 100644
--- a/packages/block-library/src/pullquote/deprecated.js
+++ b/packages/block-library/src/pullquote/deprecated.js
@@ -14,12 +14,6 @@ import {
useBlockProps,
} from '@wordpress/block-editor';
import { select } from '@wordpress/data';
-import {
- create,
- replace,
- toHTMLString,
- __UNSTABLE_LINE_SEPARATOR,
-} from '@wordpress/rich-text';
/**
* Internal dependencies
@@ -64,13 +58,14 @@ function parseBorderColor( styleString ) {
}
function multilineToInline( value ) {
- return toHTMLString( {
- value: replace(
- create( { html: value, multilineTag: 'p' } ),
- new RegExp( __UNSTABLE_LINE_SEPARATOR, 'g' ),
- '\n'
- ),
- } );
+ value = value || `
`;
+ const padded = `
${ value }`;
+ const values = padded.split( `
` );
+
+ values.shift();
+ values.pop();
+
+ return values.join( ' ' );
}
const v5 = {
diff --git a/packages/editor/README.md b/packages/editor/README.md
index dc8ec694de07a6..157136b28d0d66 100644
--- a/packages/editor/README.md
+++ b/packages/editor/README.md
@@ -91,11 +91,6 @@ The following properties (non-exhaustive list) are made available:
- `placeholder: string` - A text hint to be shown to the user when the field
value is empty, similar to the
[`input` and `textarea` attribute of the same name](https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/HTML5_updates#The_placeholder_attribute).
-- `multiline: String` - A tag name to use for the tag that should be inserted
- when Enter is pressed. For example: `li` in a list block, and `p` for a
- block that can contain multiple paragraphs. The default is that only inline
- elements are allowed to be used in inserted into the text, effectively
- disabling the behavior of the "Enter" key.
Example:
diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md
index b88d0ffea6b521..84f33bc3afaf19 100644
--- a/packages/rich-text/README.md
+++ b/packages/rich-text/README.md
@@ -151,7 +151,7 @@ _Returns_
### create
-Create a RichText value from an `Element` tree (DOM), an HTML string or a plain text string, with optionally a `Range` object to set the selection. If called without any input, an empty value will be created. If `multilineTag` is provided, any content of direct children whose type matches `multilineTag` will be separated by two newlines. The optional functions can be used to filter out content.
+Create a RichText value from an `Element` tree (DOM), an HTML string or a plain text string, with optionally a `Range` object to set the selection. If called without any input, an empty value will be created. The optional functions can be used to filter out content.
A value will have the following shape, which you are strongly encouraged not to modify without the use of helper functions:
@@ -174,8 +174,6 @@ _Parameters_
- _$1.text_ `[string]`: Text to create value from.
- _$1.html_ `[string]`: HTML to create value from.
- _$1.range_ `[Range]`: Range to create value from.
-- _$1.multilineTag_ `[string]`: Multiline tag if the structure is multiline.
-- _$1.multilineWrapperTags_ `[Array]`: Tags where lines can be found if nesting is possible.
- _$1.preserveWhiteSpace_ `[boolean]`: Whether or not to collapse white space characters.
- _$1.\_\_unstableIsEditableTree_ `[boolean]`:
@@ -416,13 +414,12 @@ _Returns_
### toHTMLString
-Create an HTML string from a Rich Text value. If a `multilineTag` is provided, text separated by a line separator will be wrapped in it.
+Create an HTML string from a Rich Text value.
_Parameters_
- _$1_ `Object`: Named argements.
- _$1.value_ `RichTextValue`: Rich text value.
-- _$1.multilineTag_ `[string]`: Multiline tag.
- _$1.preserveWhiteSpace_ `[boolean]`: Whether or not to use newline characters for line breaks.
_Returns_
diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js
index 2b437e4436777b..4aaa0b88ebc148 100644
--- a/packages/rich-text/src/component/index.js
+++ b/packages/rich-text/src/component/index.js
@@ -28,7 +28,6 @@ export function useRichText( {
preserveWhiteSpace,
onSelectionChange,
onChange,
- __unstableMultilineTag: multilineTag,
__unstableDisableFormats: disableFormats,
__unstableIsSelected: isSelected,
__unstableDependencies = [],
@@ -51,9 +50,6 @@ export function useRichText( {
return create( {
element: ref.current,
range,
- multilineTag,
- multilineWrapperTags:
- multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined,
__unstableIsEditableTree: true,
preserveWhiteSpace,
} );
@@ -63,9 +59,6 @@ export function useRichText( {
apply( {
value: newRecord,
current: ref.current,
- multilineTag,
- multilineWrapperTags:
- multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined,
prepareEditableTree: __unstableAddInvisibleFormats,
__unstableDomOnly: domOnly,
placeholder,
@@ -80,9 +73,6 @@ export function useRichText( {
_value.current = value;
record.current = create( {
html: value,
- multilineTag,
- multilineWrapperTags:
- multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined,
preserveWhiteSpace,
} );
if ( disableFormats ) {
@@ -149,7 +139,6 @@ export function useRichText( {
formats: __unstableBeforeSerialize( newRecord ),
}
: newRecord,
- multilineTag,
preserveWhiteSpace,
} );
}
@@ -179,7 +168,6 @@ export function useRichText( {
formats: __unstableBeforeSerialize( newRecord ),
}
: newRecord,
- multilineTag,
preserveWhiteSpace,
} );
@@ -227,13 +215,12 @@ export function useRichText( {
ref,
useDefaultStyle(),
useBoundaryStyle( { record } ),
- useCopyHandler( { record, multilineTag, preserveWhiteSpace } ),
+ useCopyHandler( { record, preserveWhiteSpace } ),
useSelectObject(),
useFormatBoundaries( { record, applyRecord } ),
useDelete( {
createRecord,
handleChange,
- multilineTag,
} ),
useInputAndSelection( {
record,
diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js
index 14fd806d95b3a6..e4beb09acaa443 100644
--- a/packages/rich-text/src/component/index.native.js
+++ b/packages/rich-text/src/component/index.native.js
@@ -40,10 +40,9 @@ import { getActiveFormat } from '../get-active-format';
import { getActiveFormats } from '../get-active-formats';
import { insert } from '../insert';
import { getTextContent } from '../get-text-content';
-import { isEmpty, isEmptyLine } from '../is-empty';
+import { isEmpty } from '../is-empty';
import { create } from '../create';
import { toHTMLString } from '../to-html-string';
-import { removeLineSeparator } from '../remove-line-separator';
import { isCollapsed } from '../is-collapsed';
import { remove } from '../remove';
import { getFormatColors } from '../get-format-colors';
@@ -436,7 +435,7 @@ export class RichText extends Component {
}
const isReverse = keyCode === BACKSPACE;
- const { onDelete, __unstableMultilineTag: multilineTag } = this.props;
+ const { onDelete } = this.props;
this.lastEventCount = event.nativeEvent.eventCount;
this.comesFromAztec = true;
this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged;
@@ -452,24 +451,6 @@ export class RichText extends Component {
return;
}
- if ( multilineTag ) {
- if (
- isReverse &&
- value.start === 0 &&
- value.end === 0 &&
- isEmptyLine( value )
- ) {
- newValue = removeLineSeparator( value, ! isReverse );
- } else {
- newValue = removeLineSeparator( value, isReverse );
- }
- if ( newValue ) {
- this.onFormatChange( newValue );
- event.preventDefault();
- return;
- }
- }
-
// Only process delete if the key press occurs at an uncollapsed edge.
if (
! onDelete ||
diff --git a/packages/rich-text/src/component/use-copy-handler.js b/packages/rich-text/src/component/use-copy-handler.js
index b25e64428a1b4d..c62d83351971c3 100644
--- a/packages/rich-text/src/component/use-copy-handler.js
+++ b/packages/rich-text/src/component/use-copy-handler.js
@@ -17,8 +17,7 @@ export function useCopyHandler( props ) {
propsRef.current = props;
return useRefEffect( ( element ) => {
function onCopy( event ) {
- const { record, multilineTag, preserveWhiteSpace } =
- propsRef.current;
+ const { record, preserveWhiteSpace } = propsRef.current;
const { ownerDocument } = element;
if (
isCollapsed( record.current ) ||
@@ -33,7 +32,6 @@ export function useCopyHandler( props ) {
let html = toHTMLString( {
value: selectedRecord,
- multilineTag,
preserveWhiteSpace,
} );
diff --git a/packages/rich-text/src/component/use-delete.js b/packages/rich-text/src/component/use-delete.js
index 3694db42c41b39..69ccd0ee0dc0b6 100644
--- a/packages/rich-text/src/component/use-delete.js
+++ b/packages/rich-text/src/component/use-delete.js
@@ -9,8 +9,6 @@ import { BACKSPACE, DELETE } from '@wordpress/keycodes';
* Internal dependencies
*/
import { remove } from '../remove';
-import { removeLineSeparator } from '../remove-line-separator';
-import { isEmptyLine } from '../is-empty';
export function useDelete( props ) {
const propsRef = useRef( props );
@@ -18,8 +16,7 @@ export function useDelete( props ) {
return useRefEffect( ( element ) => {
function onKeyDown( event ) {
const { keyCode } = event;
- const { createRecord, handleChange, multilineTag } =
- propsRef.current;
+ const { createRecord, handleChange } = propsRef.current;
if ( event.defaultPrevented ) {
return;
@@ -31,34 +28,11 @@ export function useDelete( props ) {
const currentValue = createRecord();
const { start, end, text } = currentValue;
- const isReverse = keyCode === BACKSPACE;
// Always handle full content deletion ourselves.
if ( start === 0 && end !== 0 && end === text.length ) {
handleChange( remove( currentValue ) );
event.preventDefault();
- return;
- }
-
- if ( multilineTag ) {
- let newValue;
-
- // Check to see if we should remove the first item if empty.
- if (
- isReverse &&
- currentValue.start === 0 &&
- currentValue.end === 0 &&
- isEmptyLine( currentValue )
- ) {
- newValue = removeLineSeparator( currentValue, ! isReverse );
- } else {
- newValue = removeLineSeparator( currentValue, isReverse );
- }
-
- if ( newValue ) {
- handleChange( newValue );
- event.preventDefault();
- }
}
}
diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js
index fa2befc603b7e4..793bdca77f71d5 100644
--- a/packages/rich-text/src/create.js
+++ b/packages/rich-text/src/create.js
@@ -9,11 +9,7 @@ import { select } from '@wordpress/data';
import { store as richTextStore } from './store';
import { createElement } from './create-element';
import { mergePair } from './concat';
-import {
- LINE_SEPARATOR,
- OBJECT_REPLACEMENT_CHARACTER,
- ZWNBSP,
-} from './special-characters';
+import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters';
/** @typedef {import('./types').RichTextValue} RichTextValue */
@@ -111,10 +107,8 @@ function toFormat( { tagName, attributes } ) {
/**
* Create a RichText value from an `Element` tree (DOM), an HTML string or a
* plain text string, with optionally a `Range` object to set the selection. If
- * called without any input, an empty value will be created. If
- * `multilineTag` is provided, any content of direct children whose type matches
- * `multilineTag` will be separated by two newlines. The optional functions can
- * be used to filter out content.
+ * called without any input, an empty value will be created. The optional
+ * functions can be used to filter out content.
*
* A value will have the following shape, which you are strongly encouraged not
* to modify without the use of helper functions:
@@ -141,12 +135,8 @@ function toFormat( { tagName, attributes } ) {
* @param {string} [$1.text] Text to create value from.
* @param {string} [$1.html] HTML to create value from.
* @param {Range} [$1.range] Range to create value from.
- * @param {string} [$1.multilineTag] Multiline tag if the structure is
- * multiline.
- * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if
- * nesting is possible.
- * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white
- * space characters.
+ * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse
+ * white space characters.
* @param {boolean} [$1.__unstableIsEditableTree]
*
* @return {RichTextValue} A rich text value.
@@ -156,8 +146,6 @@ export function create( {
text,
html,
range,
- multilineTag,
- multilineWrapperTags,
__unstableIsEditableTree: isEditableTree,
preserveWhiteSpace,
} = {} ) {
@@ -179,20 +167,9 @@ export function create( {
return createEmptyValue();
}
- if ( ! multilineTag ) {
- return createFromElement( {
- element,
- range,
- isEditableTree,
- preserveWhiteSpace,
- } );
- }
-
- return createFromMultilineElement( {
+ return createFromElement( {
element,
range,
- multilineTag,
- multilineWrapperTags,
isEditableTree,
preserveWhiteSpace,
} );
@@ -317,16 +294,11 @@ export function removeReservedCharacters( string ) {
/**
* Creates a Rich Text value from a DOM element and range.
*
- * @param {Object} $1 Named argements.
- * @param {Element} [$1.element] Element to create value from.
- * @param {Range} [$1.range] Range to create value from.
- * @param {string} [$1.multilineTag] Multiline tag if the structure is
- * multiline.
- * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if
- * nesting is possible.
- * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white
- * space characters.
- * @param {Array} [$1.currentWrapperTags]
+ * @param {Object} $1 Named argements.
+ * @param {Element} [$1.element] Element to create value from.
+ * @param {Range} [$1.range] Range to create value from.
+ * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white
+ * space characters.
* @param {boolean} [$1.isEditableTree]
*
* @return {RichTextValue} A rich text value.
@@ -334,9 +306,6 @@ export function removeReservedCharacters( string ) {
function createFromElement( {
element,
range,
- multilineTag,
- multilineWrapperTags,
- currentWrapperTags = [],
isEditableTree,
preserveWhiteSpace,
} ) {
@@ -444,30 +413,9 @@ function createFromElement( {
if ( format ) delete format.formatType;
- if (
- multilineWrapperTags &&
- multilineWrapperTags.indexOf( tagName ) !== -1
- ) {
- const value = createFromMultilineElement( {
- element: node,
- range,
- multilineTag,
- multilineWrapperTags,
- currentWrapperTags: [ ...currentWrapperTags, format ],
- isEditableTree,
- preserveWhiteSpace,
- } );
-
- accumulateSelection( accumulator, node, range, value );
- mergePair( accumulator, value );
- continue;
- }
-
const value = createFromElement( {
element: node,
range,
- multilineTag,
- multilineWrapperTags,
isEditableTree,
preserveWhiteSpace,
} );
@@ -516,79 +464,6 @@ function createFromElement( {
return accumulator;
}
-/**
- * Creates a rich text value from a DOM element and range that should be
- * multiline.
- *
- * @param {Object} $1 Named argements.
- * @param {Element} [$1.element] Element to create value from.
- * @param {Range} [$1.range] Range to create value from.
- * @param {string} [$1.multilineTag] Multiline tag if the structure is
- * multiline.
- * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if
- * nesting is possible.
- * @param {Array} [$1.currentWrapperTags] Whether to prepend a line
- * separator.
- * @param {boolean} [$1.preserveWhiteSpace] Whether or not to collapse white
- * space characters.
- * @param {boolean} [$1.isEditableTree]
- *
- * @return {RichTextValue} A rich text value.
- */
-function createFromMultilineElement( {
- element,
- range,
- multilineTag,
- multilineWrapperTags,
- currentWrapperTags = [],
- isEditableTree,
- preserveWhiteSpace,
-} ) {
- const accumulator = createEmptyValue();
-
- if ( ! element || ! element.hasChildNodes() ) {
- return accumulator;
- }
-
- const length = element.children.length;
-
- // Optimise for speed.
- for ( let index = 0; index < length; index++ ) {
- const node = element.children[ index ];
-
- if ( node.nodeName.toLowerCase() !== multilineTag ) {
- continue;
- }
-
- const value = createFromElement( {
- element: node,
- range,
- multilineTag,
- multilineWrapperTags,
- currentWrapperTags,
- isEditableTree,
- preserveWhiteSpace,
- } );
-
- // Multiline value text should be separated by a line separator.
- if ( index !== 0 || currentWrapperTags.length > 0 ) {
- mergePair( accumulator, {
- formats: [ , ],
- replacements:
- currentWrapperTags.length > 0
- ? [ currentWrapperTags ]
- : [ , ],
- text: LINE_SEPARATOR,
- } );
- }
-
- accumulateSelection( accumulator, node, range, value );
- mergePair( accumulator, value );
- }
-
- return accumulator;
-}
-
/**
* Gets the attributes of an element in object shape.
*
diff --git a/packages/rich-text/src/get-text-content.js b/packages/rich-text/src/get-text-content.js
index f400e4b56497f6..71fc9e7a749f0b 100644
--- a/packages/rich-text/src/get-text-content.js
+++ b/packages/rich-text/src/get-text-content.js
@@ -1,18 +1,10 @@
/**
* Internal dependencies
*/
-import {
- OBJECT_REPLACEMENT_CHARACTER,
- LINE_SEPARATOR,
-} from './special-characters';
+import { OBJECT_REPLACEMENT_CHARACTER } from './special-characters';
/** @typedef {import('./types').RichTextValue} RichTextValue */
-const pattern = new RegExp(
- `[${ OBJECT_REPLACEMENT_CHARACTER }${ LINE_SEPARATOR }]`,
- 'g'
-);
-
/**
* Get the textual content of a Rich Text value. This is similar to
* `Element.textContent`.
@@ -22,7 +14,5 @@ const pattern = new RegExp(
* @return {string} The text content.
*/
export function getTextContent( { text } ) {
- return text.replace( pattern, ( c ) =>
- c === OBJECT_REPLACEMENT_CHARACTER ? '' : '\n'
- );
+ return text.replace( OBJECT_REPLACEMENT_CHARACTER, '' );
}
diff --git a/packages/rich-text/src/index.ts b/packages/rich-text/src/index.ts
index 1f59320df3a63c..f487845a4cd76f 100644
--- a/packages/rich-text/src/index.ts
+++ b/packages/rich-text/src/index.ts
@@ -7,21 +7,19 @@ export { getActiveFormats } from './get-active-formats';
export { getActiveObject } from './get-active-object';
export { getTextContent } from './get-text-content';
export { isCollapsed } from './is-collapsed';
-export { isEmpty, isEmptyLine as __unstableIsEmptyLine } from './is-empty';
+export { isEmpty } from './is-empty';
export { join } from './join';
export { registerFormatType } from './register-format-type';
export { removeFormat } from './remove-format';
export { remove } from './remove';
export { replace } from './replace';
export { insert } from './insert';
-export { insertLineSeparator as __unstableInsertLineSeparator } from './insert-line-separator';
export { insertObject } from './insert-object';
export { slice } from './slice';
export { split } from './split';
export { toDom as __unstableToDom } from './to-dom';
export { toHTMLString } from './to-html-string';
export { toggleFormat } from './toggle-format';
-export { LINE_SEPARATOR as __UNSTABLE_LINE_SEPARATOR } from './special-characters';
export { unregisterFormatType } from './unregister-format-type';
export { createElement as __unstableCreateElement } from './create-element';
diff --git a/packages/rich-text/src/insert-line-separator.js b/packages/rich-text/src/insert-line-separator.js
deleted file mode 100644
index d7a5aa0f97593f..00000000000000
--- a/packages/rich-text/src/insert-line-separator.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Internal dependencies
- */
-
-import { insert } from './insert';
-import { LINE_SEPARATOR } from './special-characters';
-
-/** @typedef {import('./types').RichTextValue} RichTextValue */
-
-/**
- * Insert a line break character into a Rich Text value at the given
- * `startIndex`. Any content between `startIndex` and `endIndex` will be
- * removed. Indices are retrieved from the selection if none are provided.
- *
- * @param {RichTextValue} value Value to modify.
- * @param {number} [startIndex] Start index.
- * @param {number} [endIndex] End index.
- *
- * @return {RichTextValue} A new value with the value inserted.
- */
-export function insertLineSeparator(
- value,
- startIndex = value.start,
- endIndex = value.end
-) {
- const beforeText = value.text.slice( 0, startIndex );
- const previousLineSeparatorIndex = beforeText.lastIndexOf( LINE_SEPARATOR );
- const previousLineSeparatorFormats =
- value.replacements[ previousLineSeparatorIndex ];
- let replacements = [ , ];
-
- if ( previousLineSeparatorFormats ) {
- replacements = [ previousLineSeparatorFormats ];
- }
-
- const valueToInsert = {
- formats: [ , ],
- replacements,
- text: LINE_SEPARATOR,
- };
-
- return insert( value, valueToInsert, startIndex, endIndex );
-}
diff --git a/packages/rich-text/src/is-empty.js b/packages/rich-text/src/is-empty.js
index 7baf296bd2a3d6..86bc64bb36b565 100644
--- a/packages/rich-text/src/is-empty.js
+++ b/packages/rich-text/src/is-empty.js
@@ -1,8 +1,3 @@
-/**
- * Internal dependencies
- */
-import { LINE_SEPARATOR } from './special-characters';
-
/** @typedef {import('./types').RichTextValue} RichTextValue */
/**
@@ -16,34 +11,3 @@ import { LINE_SEPARATOR } from './special-characters';
export function isEmpty( { text } ) {
return text.length === 0;
}
-
-/**
- * Check if the current collapsed selection is on an empty line in case of a
- * multiline value.
- *
- * @param {RichTextValue} value Value te check.
- *
- * @return {boolean} True if the line is empty, false if not.
- */
-export function isEmptyLine( { text, start, end } ) {
- if ( start !== end ) {
- return false;
- }
-
- if ( text.length === 0 ) {
- return true;
- }
-
- if ( start === 0 && text.slice( 0, 1 ) === LINE_SEPARATOR ) {
- return true;
- }
-
- if ( start === text.length && text.slice( -1 ) === LINE_SEPARATOR ) {
- return true;
- }
-
- return (
- text.slice( start - 1, end + 1 ) ===
- `${ LINE_SEPARATOR }${ LINE_SEPARATOR }`
- );
-}
diff --git a/packages/rich-text/src/remove-line-separator.js b/packages/rich-text/src/remove-line-separator.js
deleted file mode 100644
index fa45616a45a72a..00000000000000
--- a/packages/rich-text/src/remove-line-separator.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Internal dependencies
- */
-
-import { LINE_SEPARATOR } from './special-characters';
-import { isCollapsed } from './is-collapsed';
-import { remove } from './remove';
-
-/** @typedef {import('./types').RichTextValue} RichTextValue */
-
-/**
- * Removes a line separator character, if existing, from a Rich Text value at
- * the current indices. If no line separator exists on the indices it will
- * return undefined.
- *
- * @param {RichTextValue} value Value to modify.
- * @param {boolean} backward Indicates if are removing from the start
- * index or the end index.
- *
- * @return {RichTextValue|undefined} A new value with the line separator
- * removed. Or undefined if no line separator
- * is found on the position.
- */
-export function removeLineSeparator( value, backward = true ) {
- const { replacements, text, start, end } = value;
- const collapsed = isCollapsed( value );
- let index = start - 1;
- let removeStart = collapsed ? start - 1 : start;
- let removeEnd = end;
- if ( ! backward ) {
- index = end;
- removeStart = start;
- removeEnd = collapsed ? end + 1 : end;
- }
-
- if ( text[ index ] !== LINE_SEPARATOR ) {
- return;
- }
-
- let newValue;
- // If the line separator that is about te be removed
- // contains wrappers, remove the wrappers first.
- if ( collapsed && replacements[ index ] && replacements[ index ].length ) {
- const newReplacements = replacements.slice();
-
- newReplacements[ index ] = replacements[ index ].slice( 0, -1 );
- newValue = {
- ...value,
- replacements: newReplacements,
- };
- } else {
- newValue = remove( value, removeStart, removeEnd );
- }
- return newValue;
-}
diff --git a/packages/rich-text/src/special-characters.js b/packages/rich-text/src/special-characters.js
index 078525ec1777e6..a05f614daf94b7 100644
--- a/packages/rich-text/src/special-characters.js
+++ b/packages/rich-text/src/special-characters.js
@@ -1,8 +1,3 @@
-/**
- * Line separator character, used for multiline text.
- */
-export const LINE_SEPARATOR = '\u2028';
-
/**
* Object replacement character, used as a placeholder for objects.
*/
diff --git a/packages/rich-text/src/split.js b/packages/rich-text/src/split.js
index cf329d7ef0985e..1853fb7bce585d 100644
--- a/packages/rich-text/src/split.js
+++ b/packages/rich-text/src/split.js
@@ -2,8 +2,6 @@
* Internal dependencies
*/
-import { replace } from './replace';
-
/** @typedef {import('./types').RichTextValue} RichTextValue */
/**
@@ -76,9 +74,5 @@ function splitAtSelection(
end: 0,
};
- return [
- // Ensure newlines are trimmed.
- replace( before, /\u2028+$/, '' ),
- replace( after, /^\u2028+/, '' ),
- ];
+ return [ before, after ];
}
diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap
index a0eb7b3cb0a3ce..5e2fbcac00de67 100644
--- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap
+++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap
@@ -201,114 +201,6 @@ exports[`recordToDom should handle double br 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- one
-
-
- a
-
-
- b
-
-
- 1
-
-
- 2
-
-
-
-
-
-
- three
-
-
-
- one
-
-
- two
-
-
-
- one
-
-
-
- one
-
-
-
-
-
-
-
-
-
-
-
-
a
@@ -323,22 +215,6 @@ exports[`recordToDom should handle selection before br 1`] = `
-
-
- one
-
-
-
-
- two
-
-
-
-
test
diff --git a/packages/rich-text/src/test/create.js b/packages/rich-text/src/test/create.js
index 3f79f4f0fb78ef..212496c8cf6b8c 100644
--- a/packages/rich-text/src/test/create.js
+++ b/packages/rich-text/src/test/create.js
@@ -17,39 +17,28 @@ describe( 'create', () => {
require( '../store' );
} );
- spec.forEach(
- ( {
- description,
- multilineTag,
- multilineWrapperTags,
- html,
- createRange,
- record,
- } ) => {
- if ( html === undefined ) {
- return;
- }
+ spec.forEach( ( { description, html, createRange, record } ) => {
+ if ( html === undefined ) {
+ return;
+ }
- // eslint-disable-next-line jest/valid-title
- it( description, () => {
- const element = createElement( document, html );
- const range = createRange( element );
- const createdRecord = create( {
- element,
- range,
- multilineTag,
- multilineWrapperTags,
- } );
- const formatsLength = getSparseArrayLength( record.formats );
- const createdFormatsLength = getSparseArrayLength(
- createdRecord.formats
- );
-
- expect( createdRecord ).toEqual( record );
- expect( createdFormatsLength ).toEqual( formatsLength );
+ // eslint-disable-next-line jest/valid-title
+ it( description, () => {
+ const element = createElement( document, html );
+ const range = createRange( element );
+ const createdRecord = create( {
+ element,
+ range,
} );
- }
- );
+ const formatsLength = getSparseArrayLength( record.formats );
+ const createdFormatsLength = getSparseArrayLength(
+ createdRecord.formats
+ );
+
+ expect( createdRecord ).toEqual( record );
+ expect( createdFormatsLength ).toEqual( formatsLength );
+ } );
+ } );
specWithRegistration.forEach(
( {
diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js
index ae7521e55e25bf..cff9daa3e24ece 100644
--- a/packages/rich-text/src/test/helpers/index.js
+++ b/packages/rich-text/src/test/helpers/index.js
@@ -11,8 +11,6 @@ const em = { type: 'em' };
const strong = { type: 'strong' };
const img = { type: 'img', attributes: { src: '' } };
const a = { type: 'a', attributes: { href: '#' } };
-const ul = { type: 'ul' };
-const ol = { type: 'ol' };
export const spec = [
{
@@ -440,200 +438,6 @@ export const spec = [
end: 2,
},
},
- {
- description: 'should handle empty multiline value',
- multilineTag: 'p',
- html: '
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.firstChild,
- endOffset: 0,
- endContainer: element.firstChild,
- } ),
- startPath: [ 0, 0, 0 ],
- endPath: [ 0, 0, 0 ],
- record: {
- start: 0,
- end: 0,
- formats: [],
- replacements: [],
- text: '',
- },
- },
- {
- description: 'should handle multiline value',
- multilineTag: 'p',
- html: 'one
two
',
- createRange: ( element ) => ( {
- startOffset: 1,
- startContainer: element.querySelector( 'p' ).firstChild,
- endOffset: 0,
- endContainer: element.lastChild,
- } ),
- startPath: [ 0, 0, 1 ],
- endPath: [ 1, 0, 0 ],
- record: {
- start: 1,
- end: 4,
- formats: [ , , , , , , , ],
- replacements: [ , , , , , , , ],
- text: 'one\u2028two',
- },
- },
- {
- description: 'should handle multiline list value',
- multilineTag: 'li',
- multilineWrapperTags: [ 'ul', 'ol' ],
- html: 'one three ',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element,
- endOffset: 1,
- endContainer: element.querySelector( 'ol > li' ).firstChild,
- } ),
- startPath: [ 0, 0, 0 ],
- endPath: [ 0, 1, 1, 1, 0, 0, 1 ],
- record: {
- start: 0,
- end: 9,
- formats: [ , , , , , , , , , , , , , , , , , ],
- replacements: [
- ,
- ,
- ,
- [ ul ],
- ,
- [ ul ],
- ,
- [ ul, ol ],
- ,
- [ ul, ol ],
- ,
- ,
- ,
- ,
- ,
- ,
- ,
- ],
- text: 'one\u2028a\u2028b\u20281\u20282\u2028three',
- },
- },
- {
- description: 'should handle empty list value',
- multilineTag: 'li',
- multilineWrapperTags: [ 'ul', 'ol' ],
- html: ' ',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.firstChild,
- endOffset: 0,
- endContainer: element.firstChild,
- } ),
- startPath: [ 0, 0, 0 ],
- endPath: [ 0, 0, 0 ],
- record: {
- start: 0,
- end: 0,
- formats: [],
- replacements: [],
- text: '',
- },
- },
- {
- description: 'should handle nested empty list value',
- multilineTag: 'li',
- multilineWrapperTags: [ 'ul', 'ol' ],
- html: ' ',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.querySelector( 'ul > li' ),
- endOffset: 0,
- endContainer: element.querySelector( 'ul > li' ),
- } ),
- startPath: [ 0, 2, 0, 0, 0 ],
- endPath: [ 0, 2, 0, 0, 0 ],
- record: {
- start: 1,
- end: 1,
- formats: [ , ],
- replacements: [ [ ul ] ],
- text: '\u2028',
- },
- },
- {
- description: 'should handle middle empty list value',
- multilineTag: 'li',
- multilineWrapperTags: [ 'ul', 'ol' ],
- html: ' ',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.firstChild.nextSibling,
- endOffset: 0,
- endContainer: element.firstChild.nextSibling,
- } ),
- startPath: [ 1, 1, 1 ],
- endPath: [ 1, 1, 1 ],
- record: {
- start: 1,
- end: 1,
- formats: [ , , ],
- replacements: [ , , ],
- text: '\u2028\u2028',
- },
- },
- {
- description: 'should handle multiline value with empty',
- multilineTag: 'p',
- html: 'one
',
- createRange: ( element ) => ( {
- startOffset: 0,
- startContainer: element.lastChild,
- endOffset: 0,
- endContainer: element.lastChild,
- } ),
- startPath: [ 1, 0, 0 ],
- endPath: [ 1, 0, 0 ],
- record: {
- start: 4,
- end: 4,
- formats: [ , , , , ],
- replacements: [ , , , , ],
- text: 'one\u2028',
- },
- },
- {
- description: 'should handle multiline value with element selection',
- multilineTag: 'li',
- multilineWrapperTags: [ 'ul', 'ol' ],
- html: 'one ',
- createRange: ( element ) => ( {
- startOffset: 1,
- startContainer: element.firstChild,
- endOffset: 1,
- endContainer: element.firstChild,
- } ),
- startPath: [ 0, 0, 3 ],
- endPath: [ 0, 0, 3 ],
- record: {
- start: 3,
- end: 3,
- formats: [ , , , ],
- replacements: [ , , , ],
- text: 'one',
- },
- },
- {
- description: 'should ignore formats at line separator',
- multilineTag: 'p',
- startPath: [],
- endPath: [],
- record: {
- formats: [ [ em ], [ em ], [ em ], [ em ], [ em ], [ em ], [ em ] ],
- replacements: [ , , , , , , , ],
- text: 'one\u2028two',
- },
- },
{
description: 'should remove padding',
html: ZWNBSP,
diff --git a/packages/rich-text/src/test/insert-line-separator.js b/packages/rich-text/src/test/insert-line-separator.js
deleted file mode 100644
index 497555cc4a01a0..00000000000000
--- a/packages/rich-text/src/test/insert-line-separator.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * External dependencies
- */
-import deepFreeze from 'deep-freeze';
-
-/**
- * Internal dependencies
- */
-
-import { insertLineSeparator } from '../insert-line-separator';
-import { LINE_SEPARATOR } from '../special-characters';
-import { getSparseArrayLength } from './helpers';
-
-describe( 'insertLineSeparator', () => {
- const ol = { type: 'ol' };
-
- it( 'should insert line separator at end', () => {
- const value = {
- formats: [ , ],
- replacements: [ , ],
- text: '1',
- start: 1,
- end: 1,
- };
- const expected = {
- formats: [ , , ],
- replacements: [ , , ],
- text: `1${ LINE_SEPARATOR }`,
- start: 2,
- end: 2,
- };
- const result = insertLineSeparator( deepFreeze( value ) );
-
- expect( result ).not.toBe( value );
- expect( result ).toEqual( expected );
- expect( getSparseArrayLength( result.replacements ) ).toBe( 0 );
- } );
-
- it( 'should insert line separator at start', () => {
- const value = {
- formats: [ , ],
- replacements: [ , ],
- text: '1',
- start: 0,
- end: 0,
- };
- const expected = {
- formats: [ , , ],
- replacements: [ , , ],
- text: `${ LINE_SEPARATOR }1`,
- start: 1,
- end: 1,
- };
- const result = insertLineSeparator( deepFreeze( value ) );
-
- expect( result ).not.toBe( value );
- expect( result ).toEqual( expected );
- expect( getSparseArrayLength( result.replacements ) ).toBe( 0 );
- } );
-
- it( 'should insert line separator with previous line separator formats', () => {
- const value = {
- formats: [ , , , , , ],
- replacements: [ , , , [ ol ], , ],
- text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a`,
- start: 5,
- end: 5,
- };
- const expected = {
- formats: [ , , , , , , ],
- replacements: [ , , , [ ol ], , [ ol ] ],
- text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a${ LINE_SEPARATOR }`,
- start: 6,
- end: 6,
- };
- const result = insertLineSeparator( deepFreeze( value ) );
-
- expect( result ).not.toBe( value );
- expect( result ).toEqual( expected );
- expect( getSparseArrayLength( result.replacements ) ).toBe( 2 );
- } );
-
- it( 'should insert line separator without formats if previous line separator did not have any', () => {
- const value = {
- formats: [ , , , , , ],
- replacements: [ , , , , , ],
- text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a`,
- start: 5,
- end: 5,
- };
- const expected = {
- formats: [ , , , , , , ],
- replacements: [ , , , , , , ],
- text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }a${ LINE_SEPARATOR }`,
- start: 6,
- end: 6,
- };
- const result = insertLineSeparator( deepFreeze( value ) );
-
- expect( result ).not.toBe( value );
- expect( result ).toEqual( expected );
- expect( getSparseArrayLength( result.replacements ) ).toBe( 0 );
- } );
-} );
diff --git a/packages/rich-text/src/test/is-empty.js b/packages/rich-text/src/test/is-empty.js
index 0080ba3a0914fb..6cd1d0b6d08275 100644
--- a/packages/rich-text/src/test/is-empty.js
+++ b/packages/rich-text/src/test/is-empty.js
@@ -2,7 +2,7 @@
* Internal dependencies
*/
-import { isEmpty, isEmptyLine } from '../is-empty';
+import { isEmpty } from '../is-empty';
describe( 'isEmpty', () => {
it( 'should return true', () => {
@@ -23,62 +23,3 @@ describe( 'isEmpty', () => {
expect( isEmpty( one ) ).toBe( false );
} );
} );
-
-describe( 'isEmptyLine', () => {
- it( 'should return true', () => {
- const one = {
- formats: [],
- text: '',
- start: 0,
- end: 0,
- };
- const two = {
- formats: [ , , ],
- text: '\u2028',
- start: 0,
- end: 0,
- };
- const three = {
- formats: [ , , ],
- text: '\u2028',
- start: 1,
- end: 1,
- };
- const four = {
- formats: [ , , , , ],
- text: '\u2028\u2028',
- start: 1,
- end: 1,
- };
- const five = {
- formats: [ , , , , ],
- text: 'a\u2028\u2028b',
- start: 2,
- end: 2,
- };
-
- expect( isEmptyLine( one ) ).toBe( true );
- expect( isEmptyLine( two ) ).toBe( true );
- expect( isEmptyLine( three ) ).toBe( true );
- expect( isEmptyLine( four ) ).toBe( true );
- expect( isEmptyLine( five ) ).toBe( true );
- } );
-
- it( 'should return false', () => {
- const one = {
- formats: [ , , , , ],
- text: '\u2028a\u2028',
- start: 1,
- end: 1,
- };
- const two = {
- formats: [ , , , , ],
- text: '\u2028\n',
- start: 1,
- end: 1,
- };
-
- expect( isEmptyLine( one ) ).toBe( false );
- expect( isEmptyLine( two ) ).toBe( false );
- } );
-} );
diff --git a/packages/rich-text/src/test/split.js b/packages/rich-text/src/test/split.js
index 7de61c1b3efeb8..05a76889ee6864 100644
--- a/packages/rich-text/src/test/split.js
+++ b/packages/rich-text/src/test/split.js
@@ -112,39 +112,6 @@ describe( 'split', () => {
} );
} );
- it( 'should split multiline', () => {
- const record = {
- formats: [ , , , , , , , , , , ],
- replacements: [ , , , , , , , , , , ],
- text: 'test\u2028\u2028test',
- start: 5,
- end: 5,
- };
- const expected = [
- {
- formats: [ , , , , ],
- replacements: [ , , , , ],
- text: 'test',
- },
- {
- formats: [ , , , , ],
- replacements: [ , , , , ],
- text: 'test',
- start: 0,
- end: 0,
- },
- ];
- const result = split( deepFreeze( record ) );
-
- expect( result ).toEqual( expected );
- result.forEach( ( item, index ) => {
- expect( item ).not.toBe( record );
- expect( getSparseArrayLength( item.formats ) ).toBe(
- getSparseArrayLength( expected[ index ].formats )
- );
- } );
- } );
-
it( 'should split search', () => {
const record = {
start: 6,
diff --git a/packages/rich-text/src/test/to-dom.js b/packages/rich-text/src/test/to-dom.js
index 1c6aa50623e951..4f9b2df86cad35 100644
--- a/packages/rich-text/src/test/to-dom.js
+++ b/packages/rich-text/src/test/to-dom.js
@@ -11,19 +11,16 @@ describe( 'recordToDom', () => {
require( '../store' );
} );
- spec.forEach(
- ( { description, multilineTag, record, startPath, endPath } ) => {
- // eslint-disable-next-line jest/valid-title
- it( description, () => {
- const { body, selection } = toDom( {
- value: record,
- multilineTag,
- } );
- expect( body ).toMatchSnapshot();
- expect( selection ).toEqual( { startPath, endPath } );
+ spec.forEach( ( { description, record, startPath, endPath } ) => {
+ // eslint-disable-next-line jest/valid-title
+ it( description, () => {
+ const { body, selection } = toDom( {
+ value: record,
} );
- }
- );
+ expect( body ).toMatchSnapshot();
+ expect( selection ).toEqual( { startPath, endPath } );
+ } );
+ } );
} );
describe( 'applyValue', () => {
diff --git a/packages/rich-text/src/test/to-html-string.js b/packages/rich-text/src/test/to-html-string.js
index 507359e99ddcf0..32360e6805d69f 100644
--- a/packages/rich-text/src/test/to-html-string.js
+++ b/packages/rich-text/src/test/to-html-string.js
@@ -97,21 +97,6 @@ describe( 'toHTMLString', () => {
);
} );
- it( 'should extract recreate HTML 6', () => {
- const HTML = 'one three ';
- const element = createNode( `` );
- const multilineTag = 'li';
- const multilineWrapperTags = [ 'ul', 'ol' ];
- const value = create( { element, multilineTag, multilineWrapperTags } );
- const result = toHTMLString( {
- value,
- multilineTag,
- multilineWrapperTags,
- } );
-
- expect( result ).toEqual( HTML );
- } );
-
it( 'should serialize neighbouring formats of same type', () => {
const HTML = 'a a ';
const element = createNode( `${ HTML }
` );
diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js
index 305eebaf3e4a6e..e7288e4ba16332 100644
--- a/packages/rich-text/src/to-dom.js
+++ b/packages/rich-text/src/to-dom.js
@@ -104,7 +104,6 @@ function remove( node ) {
export function toDom( {
value,
- multilineTag,
prepareEditableTree,
isEditableTree = true,
placeholder,
@@ -134,7 +133,6 @@ export function toDom( {
const tree = toTree( {
value,
- multilineTag,
createEmpty,
append,
getLastChild,
@@ -165,13 +163,11 @@ export function toDom( {
/**
* Create an `Element` tree from a Rich Text value and applies the difference to
- * the `Element` tree contained by `current`. If a `multilineTag` is provided,
- * text separated by two new lines will be wrapped in an `Element` of that type.
+ * the `Element` tree contained by `current`.
*
* @param {Object} $1 Named arguments.
* @param {RichTextValue} $1.value Value to apply.
* @param {HTMLElement} $1.current The live root node to apply the element tree to.
- * @param {string} [$1.multilineTag] Multiline tag.
* @param {Function} [$1.prepareEditableTree] Function to filter editorable formats.
* @param {boolean} [$1.__unstableDomOnly] Only apply elements, no selection.
* @param {string} [$1.placeholder] Placeholder text.
@@ -179,7 +175,6 @@ export function toDom( {
export function apply( {
value,
current,
- multilineTag,
prepareEditableTree,
__unstableDomOnly,
placeholder,
@@ -187,7 +182,6 @@ export function apply( {
// Construct a new element tree in memory.
const { body, selection } = toDom( {
value,
- multilineTag,
prepareEditableTree,
placeholder,
doc: current.ownerDocument,
diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js
index 0b2689248afb72..66ae1d82b38450 100644
--- a/packages/rich-text/src/to-html-string.js
+++ b/packages/rich-text/src/to-html-string.js
@@ -17,21 +17,18 @@ import { toTree } from './to-tree';
/** @typedef {import('./types').RichTextValue} RichTextValue */
/**
- * Create an HTML string from a Rich Text value. If a `multilineTag` is
- * provided, text separated by a line separator will be wrapped in it.
+ * Create an HTML string from a Rich Text value.
*
* @param {Object} $1 Named argements.
* @param {RichTextValue} $1.value Rich text value.
- * @param {string} [$1.multilineTag] Multiline tag.
* @param {boolean} [$1.preserveWhiteSpace] Whether or not to use newline
* characters for line breaks.
*
* @return {string} HTML string.
*/
-export function toHTMLString( { value, multilineTag, preserveWhiteSpace } ) {
+export function toHTMLString( { value, preserveWhiteSpace } ) {
const tree = toTree( {
value,
- multilineTag,
preserveWhiteSpace,
createEmpty,
append,
diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js
index 4db974aaad7142..c380570db561de 100644
--- a/packages/rich-text/src/to-tree.js
+++ b/packages/rich-text/src/to-tree.js
@@ -4,11 +4,7 @@
import { getActiveFormats } from './get-active-formats';
import { getFormatType } from './get-format-type';
-import {
- LINE_SEPARATOR,
- OBJECT_REPLACEMENT_CHARACTER,
- ZWNBSP,
-} from './special-characters';
+import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters';
function restoreOnAttributes( attributes, isEditableTree ) {
if ( isEditableTree ) {
@@ -133,7 +129,6 @@ function isEqualUntil( a, b, index ) {
export function toTree( {
value,
- multilineTag,
preserveWhiteSpace,
createEmpty,
append,
@@ -151,21 +146,13 @@ export function toTree( {
const { formats, replacements, text, start, end } = value;
const formatsLength = formats.length + 1;
const tree = createEmpty();
- const multilineFormat = { type: multilineTag };
const activeFormats = getActiveFormats( value );
const deepestActiveFormat = activeFormats[ activeFormats.length - 1 ];
- let lastSeparatorFormats;
let lastCharacterFormats;
let lastCharacter;
- // If we're building a multiline tree, start off with a multiline element.
- if ( multilineTag ) {
- append( append( tree, { type: multilineTag } ), '' );
- lastCharacterFormats = lastSeparatorFormats = [ multilineFormat ];
- } else {
- append( tree, '' );
- }
+ append( tree, '' );
for ( let i = 0; i < formatsLength; i++ ) {
const character = text.charAt( i );
@@ -173,62 +160,13 @@ export function toTree( {
isEditableTree &&
// Pad the line if the line is empty.
( ! lastCharacter ||
- lastCharacter === LINE_SEPARATOR ||
// Pad the line if the previous character is a line break, otherwise
// the line break won't be visible.
lastCharacter === '\n' );
- let characterFormats = formats[ i ];
-
- // Set multiline tags in queue for building the tree.
- if ( multilineTag ) {
- if ( character === LINE_SEPARATOR ) {
- characterFormats = lastSeparatorFormats = (
- replacements[ i ] || []
- ).reduce(
- ( accumulator, format ) => {
- accumulator.push( format, multilineFormat );
- return accumulator;
- },
- [ multilineFormat ]
- );
- } else {
- characterFormats = [
- ...lastSeparatorFormats,
- ...( characterFormats || [] ),
- ];
- }
- }
-
+ const characterFormats = formats[ i ];
let pointer = getLastChild( tree );
- if ( shouldInsertPadding && character === LINE_SEPARATOR ) {
- let node = pointer;
-
- while ( ! isText( node ) ) {
- node = getLastChild( node );
- }
-
- append( getParent( node ), ZWNBSP );
- }
-
- // Set selection for the start of line.
- if ( lastCharacter === LINE_SEPARATOR ) {
- let node = pointer;
-
- while ( ! isText( node ) ) {
- node = getLastChild( node );
- }
-
- if ( onStartIndex && start === i ) {
- onStartIndex( tree, node );
- }
-
- if ( onEndIndex && end === i ) {
- onEndIndex( tree, node );
- }
- }
-
if ( characterFormats ) {
characterFormats.forEach( ( format, formatIndex ) => {
if (
@@ -239,11 +177,7 @@ export function toTree( {
characterFormats,
lastCharacterFormats,
formatIndex
- ) &&
- // Do not reuse the last element if the character is a
- // line separator.
- ( character !== LINE_SEPARATOR ||
- characterFormats.length - 1 !== formatIndex )
+ )
) {
pointer = getLastChild( pointer );
return;
@@ -253,9 +187,7 @@ export function toTree( {
format;
const boundaryClass =
- isEditableTree &&
- character !== LINE_SEPARATOR &&
- format === deepestActiveFormat;
+ isEditableTree && format === deepestActiveFormat;
const parent = getParent( pointer );
const newNode = append(
@@ -278,13 +210,6 @@ export function toTree( {
} );
}
- // No need for further processing if the character is a line separator.
- if ( character === LINE_SEPARATOR ) {
- lastCharacterFormats = characterFormats;
- lastCharacter = character;
- continue;
- }
-
// If there is selection at 0, handle it before characters are inserted.
if ( i === 0 ) {
if ( onStartIndex && start === 0 ) {
diff --git a/schemas/json/block.json b/schemas/json/block.json
index f20fb5b0dea972..b181bf2d9ab403 100644
--- a/schemas/json/block.json
+++ b/schemas/json/block.json
@@ -173,10 +173,6 @@
"type": "string",
"description": "Use an attribute source to extract the value from an attribute in the markup. The attribute is specified by the attribute field, which must be supplied.\n\nExample: Extract the src attribute from an image found in the block’s markup."
},
- "multiline": {
- "type": "string",
- "description": "Use the multiline property to extract the inner HTML of matching tag names for the use in RichText with the multiline prop."
- },
"query": {
"type": "object",
"description": "Use query to extract an array of values from markup. Entries of the array are determined by the selector argument, where each matched element within the block will have an entry structured corresponding to the second argument, an object of attribute sources."
diff --git a/test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js b/test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js
new file mode 100644
index 00000000000000..9fa3fef903c7fc
--- /dev/null
+++ b/test/e2e/specs/editor/various/rich-text-deprecated-multiline.spec.js
@@ -0,0 +1,126 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.describe( 'RichText deprecated multiline', () => {
+ test.beforeEach( async ( { admin, page, editor } ) => {
+ await admin.createNewPost();
+ await page.evaluate( () => {
+ const registerBlockType = window.wp.blocks.registerBlockType;
+ const { useBlockProps, RichText } = window.wp.blockEditor;
+ const el = window.wp.element.createElement;
+ registerBlockType( 'core/rich-text-deprecated-multiline', {
+ apiVersion: 3,
+ title: 'Deprecated RichText multiline',
+ attributes: {
+ value: {
+ type: 'string',
+ source: 'html',
+ selector: 'blockquote',
+ },
+ },
+ edit: function Edit( { attributes, setAttributes } ) {
+ return el( RichText, {
+ ...useBlockProps(),
+ tagName: 'blockquote',
+ multiline: 'p',
+ value: attributes.value,
+ onChange( value ) {
+ setAttributes( { value } );
+ },
+ } );
+ },
+ save( { attributes } ) {
+ return el( RichText.Content, {
+ tagName: 'blockquote',
+ multiline: 'p',
+ value: attributes.value,
+ } );
+ },
+ } );
+ } );
+ await editor.insertBlock( {
+ name: 'core/rich-text-deprecated-multiline',
+ } );
+ await page.keyboard.press( 'ArrowDown' );
+ } );
+
+ test( 'should save', async ( { page, editor } ) => {
+ await page.keyboard.type( '1' );
+ await page.keyboard.press( 'Enter' );
+ await page.keyboard.type( '2' );
+
+ expect( await editor.getBlocks() ).toMatchObject( [
+ {
+ name: 'core/rich-text-deprecated-multiline',
+ attributes: {
+ value: '1
2
',
+ },
+ },
+ ] );
+
+ // Test serialised output.
+ expect( await editor.getEditedPostContent() ).toBe(
+ `
+1
2
+`
+ );
+ } );
+
+ test( 'should split in middle', async ( { page, editor } ) => {
+ await page.keyboard.type( '12' );
+ await page.keyboard.press( 'ArrowLeft' );
+ await page.keyboard.press( 'Enter' );
+ // Test selection after split.
+ await page.keyboard.type( '‸' );
+
+ expect( await editor.getBlocks() ).toMatchObject( [
+ {
+ name: 'core/rich-text-deprecated-multiline',
+ attributes: {
+ value: '1
‸2
',
+ },
+ },
+ ] );
+ } );
+
+ test( 'should merge two lines', async ( { page, editor } ) => {
+ await page.keyboard.type( '1' );
+ await page.keyboard.press( 'Enter' );
+ await page.keyboard.type( '2' );
+ await page.keyboard.press( 'ArrowLeft' );
+ await page.keyboard.press( 'Backspace' );
+ // Test selection after merge.
+ await page.keyboard.type( '‸' );
+
+ expect( await editor.getBlocks() ).toMatchObject( [
+ {
+ name: 'core/rich-text-deprecated-multiline',
+ attributes: {
+ value: '1‸2
',
+ },
+ },
+ ] );
+ } );
+
+ test( 'should merge two lines (forward)', async ( { page, editor } ) => {
+ await page.keyboard.type( '1' );
+ await page.keyboard.press( 'Enter' );
+ await page.keyboard.type( '2' );
+ await page.keyboard.press( 'ArrowLeft' );
+ await page.keyboard.press( 'ArrowLeft' );
+ await page.keyboard.press( 'Delete' );
+ // Test selection after merge.
+ await page.keyboard.type( '‸' );
+
+ expect( await editor.getBlocks() ).toMatchObject( [
+ {
+ name: 'core/rich-text-deprecated-multiline',
+ attributes: {
+ value: '1‸2
',
+ },
+ },
+ ] );
+ } );
+} );
From c10b0f6f7d6a83ade593d5ba4bcfe23a58ff5a1b Mon Sep 17 00:00:00 2001
From: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Date: Mon, 11 Sep 2023 22:42:55 +0900
Subject: [PATCH 08/42] Edit Widgets: Fix invisible action area when the top
toolbar is enabled (#54329)
---
.../src/components/block-tools/block-contextual-toolbar.js | 4 ++--
packages/edit-widgets/src/components/header/style.scss | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js
index b2087ac2ff5f6d..9ac0bcb8e1beb5 100644
--- a/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js
+++ b/packages/block-editor/src/components/block-tools/block-contextual-toolbar.js
@@ -112,9 +112,9 @@ function BlockContextualToolbar( { focusOnMount, isFixed, ...props } ) {
return;
}
- // get the width of the pinned items in the post editor
+ // get the width of the pinned items in the post editor or widget editor
const pinnedItems = document.querySelector(
- '.edit-post-header__settings'
+ '.edit-post-header__settings, .edit-widgets-header__actions'
);
// get the width of the left header in the site editor
diff --git a/packages/edit-widgets/src/components/header/style.scss b/packages/edit-widgets/src/components/header/style.scss
index 06ea6bf19d7774..64a9f124bb7502 100644
--- a/packages/edit-widgets/src/components/header/style.scss
+++ b/packages/edit-widgets/src/components/header/style.scss
@@ -3,7 +3,6 @@
align-items: center;
justify-content: space-between;
height: $header-height;
- padding: 0 $grid-unit-20;
overflow: auto;
background: #fff;
@@ -16,6 +15,7 @@
display: flex;
align-items: center;
justify-content: center;
+ padding-left: $grid-unit-20;
}
.edit-widgets-header__title {
@@ -27,7 +27,7 @@
.edit-widgets-header__actions {
display: flex;
align-items: center;
-
+ padding-right: $grid-unit-20;
gap: $grid-unit-05;
@include break-small() {
From 375c58274a7adb97d2fbd5057a252da67031b22e Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Mon, 11 Sep 2023 14:51:33 +0100
Subject: [PATCH 09/42] Fix: Remove unrequired code from BorderControl
documentation. (#54348)
---
.../components/src/border-control/border-control/README.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/packages/components/src/border-control/border-control/README.md b/packages/components/src/border-control/border-control/README.md
index 14b3df6afaf6c9..55c860720f2394 100644
--- a/packages/components/src/border-control/border-control/README.md
+++ b/packages/components/src/border-control/border-control/README.md
@@ -31,13 +31,12 @@ const colors = [
const MyBorderControl = () => {
const [ border, setBorder ] = useState();
- const onChange = ( newBorder ) => setBorder( newBorder );
return (
);
From a9a2557145f440c1abb0f06e0d1d03c5fc0418d6 Mon Sep 17 00:00:00 2001
From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com>
Date: Mon, 11 Sep 2023 16:56:56 +0200
Subject: [PATCH 10/42] Add manual SSR to the Interactivity API blocks (#54343)
* Add manual SSR to the navigation block
* Use a CSS media query to show/hide the PDF embed
---------
Co-authored-by: Luis Herranz
---
packages/block-library/src/file/index.php | 3 +--
packages/block-library/src/file/style.scss | 7 ++++++-
packages/block-library/src/file/view.js | 4 ++--
packages/block-library/src/navigation/index.php | 3 +++
4 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/packages/block-library/src/file/index.php b/packages/block-library/src/file/index.php
index 212cb571e99f7f..4d8e8e13b19717 100644
--- a/packages/block-library/src/file/index.php
+++ b/packages/block-library/src/file/index.php
@@ -59,8 +59,7 @@ static function ( $matches ) {
$processor->next_tag();
$processor->set_attribute( 'data-wp-interactive', '' );
$processor->next_tag( 'object' );
- $processor->set_attribute( 'data-wp-bind--hidden', '!selectors.core.file.hasPdfPreview' );
- $processor->set_attribute( 'hidden', true );
+ $processor->set_attribute( 'data-wp-style--display', 'selectors.core.file.hasPdfPreview' );
return $processor->get_updated_html();
}
diff --git a/packages/block-library/src/file/style.scss b/packages/block-library/src/file/style.scss
index 5c9a2f7be2adc6..11d0a8a46f321d 100644
--- a/packages/block-library/src/file/style.scss
+++ b/packages/block-library/src/file/style.scss
@@ -29,6 +29,12 @@
margin-bottom: 1em;
}
+@media (max-width: 768px) {
+ .wp-block-file__embed {
+ display: none;
+ }
+}
+
//This needs a low specificity so it won't override the rules from the button element if defined in theme.json.
:where(.wp-block-file__button) {
border-radius: 2em;
@@ -36,7 +42,6 @@
display: inline-block;
&:is(a) {
-
&:hover,
&:visited,
&:focus,
diff --git a/packages/block-library/src/file/view.js b/packages/block-library/src/file/view.js
index 9d09ca2b7f4340..51c726d0cbe5ec 100644
--- a/packages/block-library/src/file/view.js
+++ b/packages/block-library/src/file/view.js
@@ -5,13 +5,13 @@ import { store } from '@wordpress/interactivity';
/**
* Internal dependencies
*/
-import { browserSupportsPdfs as hasPdfPreview } from './utils';
+import { browserSupportsPdfs } from './utils';
store( {
selectors: {
core: {
file: {
- hasPdfPreview,
+ hasPdfPreview: browserSupportsPdfs() ? 'inherit' : 'none',
},
},
},
diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php
index f90c5f9563166d..6d5bb07335f2af 100644
--- a/packages/block-library/src/navigation/index.php
+++ b/packages/block-library/src/navigation/index.php
@@ -104,6 +104,7 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes
) ) {
$w->set_attribute( 'data-wp-on--click', 'actions.core.navigation.toggleMenuOnClick' );
$w->set_attribute( 'data-wp-bind--aria-expanded', 'selectors.core.navigation.isMenuOpen' );
+ // The `aria-expanded` attribute for SSR is already added in the submenu block.
};
// Add directives to the submenu.
if ( $w->next_tag(
@@ -713,7 +714,9 @@ function render_block_core_navigation( $attributes, $content, $block ) {
';
$responsive_dialog_directives = '
data-wp-bind--aria-modal="selectors.core.navigation.isMenuOpen"
+ aria-modal="false"
data-wp-bind--role="selectors.core.navigation.roleAttribute"
+ role=""
data-wp-effect="effects.core.navigation.focusFirstElement"
';
$close_button_directives = '
From 1eb2f1e32231da44f6cc2dcb42e0c3ab03f0db3d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 11 Sep 2023 11:15:45 -0400
Subject: [PATCH 11/42] Bump actions/cache from 3.3.1 to 3.3.2 (#54306)
Bumps [actions/cache](https://github.com/actions/cache) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8...704facf57e6136b1bc63b828d79edcd491f0ee84)
---
updated-dependencies:
- dependency-name: actions/cache
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/pull-request-automation.yml | 2 +-
.github/workflows/rnmobile-android-runner.yml | 2 +-
.github/workflows/rnmobile-ios-runner.yml | 4 ++--
.github/workflows/unit-test.yml | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml
index 6691fec3341c9d..a34df5282d6d8c 100644
--- a/.github/workflows/pull-request-automation.yml
+++ b/.github/workflows/pull-request-automation.yml
@@ -26,7 +26,7 @@ jobs:
node-version: ${{ matrix.node }}
- name: Cache NPM packages
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
+ uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml
index 4b5a4393b70c5b..1d4e010558a6e1 100644
--- a/.github/workflows/rnmobile-android-runner.yml
+++ b/.github/workflows/rnmobile-android-runner.yml
@@ -40,7 +40,7 @@ jobs:
uses: gradle/gradle-build-action@ef76a971e2fa3f867b617efd72f2fbd72cf6f8bc # v2.8.0
- name: AVD cache
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
+ uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
id: avd-cache
with:
path: |
diff --git a/.github/workflows/rnmobile-ios-runner.yml b/.github/workflows/rnmobile-ios-runner.yml
index 3b82180be5567b..6a03547966fe10 100644
--- a/.github/workflows/rnmobile-ios-runner.yml
+++ b/.github/workflows/rnmobile-ios-runner.yml
@@ -34,7 +34,7 @@ jobs:
run: find package-lock.json packages/react-native-editor/ios packages/react-native-aztec/ios packages/react-native-bridge/ios -type f -print0 | sort -z | xargs -0 shasum | tee ios-checksums.txt
- name: Restore build cache
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
+ uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: |
packages/react-native-editor/ios/build/GutenbergDemo/Build/Products/Release-iphonesimulator/GutenbergDemo.app
@@ -42,7 +42,7 @@ jobs:
key: ${{ runner.os }}-ios-build-${{ matrix.xcode }}-${{ matrix.device }}-${{ hashFiles('ios-checksums.txt') }}
- name: Restore pods cache
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
+ uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: |
packages/react-native-editor/ios/Pods
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index f48386cde43729..fd021b1b338154 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -239,7 +239,7 @@ jobs:
run: echo "date=$(/bin/date -u --date='last Mon' "+%F")" >> $GITHUB_OUTPUT
- name: Cache PHPCS scan cache
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
+ uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
with:
path: .cache/phpcs.json
key: ${{ runner.os }}-date-${{ steps.get-date.outputs.date }}-phpcs-cache-${{ hashFiles('**/composer.json', 'phpcs.xml.dist') }}
From 7c73a89241d837198a27278f2a4e62c12220c83b Mon Sep 17 00:00:00 2001
From: Andrei Draganescu
Date: Mon, 11 Sep 2023 18:17:45 +0300
Subject: [PATCH 12/42] Add aspect ratio to image placeholder (#54216)
* adds aspect ratio control and behavior to image block placeholder
* Allow the image placeholder to shrink to small sizes and then allow it to scroll
* Align the placeholder to the top
* remove resolution tool
* refactor duplicate code
---------
Co-authored-by: scruffian
---
packages/block-library/src/image/edit.js | 45 +++++----
packages/block-library/src/image/image.js | 96 ++++++++++---------
packages/components/src/placeholder/index.tsx | 1 +
.../components/src/placeholder/style.scss | 6 +-
4 files changed, 83 insertions(+), 65 deletions(-)
diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js
index 6c9505d8b2d986..2c1e0ee3a41767 100644
--- a/packages/block-library/src/image/edit.js
+++ b/packages/block-library/src/image/edit.js
@@ -111,6 +111,8 @@ export function ImageEdit( {
width,
height,
sizeSlug,
+ aspectRatio,
+ scale,
} = attributes;
const [ temporaryURL, setTemporaryURL ] = useState();
@@ -335,7 +337,16 @@ export function ImageEdit( {
instructions={ __(
'Upload an image file, pick one from your media library, or add one with a URL.'
) }
- style={ isSelected ? undefined : borderProps.style }
+ style={ {
+ aspectRatio:
+ ! ( width && height ) && aspectRatio
+ ? aspectRatio
+ : undefined,
+ width: height && aspectRatio ? '100%' : width,
+ height: width && aspectRatio ? '100%' : height,
+ objectFit: scale,
+ ...borderProps.style,
+ } }
>
{ content }
@@ -344,23 +355,21 @@ export function ImageEdit( {
return (
- { ( temporaryURL || url ) && (
-
- ) }
+
{ ! url && blockEditingMode === 'default' && (
{
+ // Rebuilding the object forces setting `undefined`
+ // for values that are removed since setAttributes
+ // doesn't do anything with keys that aren't set.
+ setAttributes( {
+ // CSS includes `height: auto`, but we need
+ // `width: auto` to fix the aspect ratio when
+ // only height is set due to the width and
+ // height attributes set via the server.
+ width: ! newWidth && newHeight ? 'auto' : newWidth,
+ height: newHeight,
+ scale: newScale,
+ aspectRatio: newAspectRatio,
+ } );
+ } }
+ defaultScale="cover"
+ defaultAspectRatio="auto"
+ scaleOptions={ scaleOptions }
+ unitsOptions={ dimensionsUnitsOptions }
+ />
+ );
+
+ const resetAll = () => {
+ setAttributes( {
+ width: undefined,
+ height: undefined,
+ scale: undefined,
+ aspectRatio: undefined,
+ } );
+ };
+
+ const sizeControls = (
+
+
+ { isResizable && dimensionsControl }
+
+
+ );
+
const controls = (
<>
@@ -443,17 +490,7 @@ export default function Image( {
) }
-
- setAttributes( {
- width: undefined,
- height: undefined,
- scale: undefined,
- aspectRatio: undefined,
- } )
- }
- >
+
{ ! multiImageSelection && (
) }
- { isResizable && (
- {
- // Rebuilding the object forces setting `undefined`
- // for values that are removed since setAttributes
- // doesn't do anything with keys that aren't set.
- setAttributes( {
- // CSS includes `height: auto`, but we need
- // `width: auto` to fix the aspect ratio when
- // only height is set due to the width and
- // height attributes set via the server.
- width:
- ! newWidth && newHeight
- ? 'auto'
- : newWidth,
- height: newHeight,
- scale: newScale,
- aspectRatio: newAspectRatio,
- } );
- } }
- defaultScale="cover"
- defaultAspectRatio="auto"
- scaleOptions={ scaleOptions }
- unitsOptions={ dimensionsUnitsOptions }
- />
- ) }
+ { isResizable && dimensionsControl }
{ /* Hide controls during upload to avoid component remount,
diff --git a/packages/components/src/placeholder/index.tsx b/packages/components/src/placeholder/index.tsx
index bbd17f9972188a..13634f6710d945 100644
--- a/packages/components/src/placeholder/index.tsx
+++ b/packages/components/src/placeholder/index.tsx
@@ -75,6 +75,7 @@ export function Placeholder(
const fieldsetClasses = classnames( 'components-placeholder__fieldset', {
'is-column-layout': isColumnLayout,
} );
+
return (
{ withIllustration ? PlaceholderIllustration : null }
diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss
index df06969852fdae..1cb3edfcfdbba7 100644
--- a/packages/components/src/placeholder/style.scss
+++ b/packages/components/src/placeholder/style.scss
@@ -3,7 +3,6 @@
box-sizing: border-box;
position: relative;
padding: 1em;
- min-height: 200px;
width: 100%;
text-align: left;
margin: 0;
@@ -17,7 +16,7 @@
@supports (position: sticky) {
display: flex;
flex-direction: column;
- justify-content: center;
+ justify-content: top;
align-items: flex-start;
}
@@ -180,7 +179,6 @@
color: inherit;
display: flex;
box-shadow: none;
- min-width: 100px;
// Blur the background so layered dashed placeholders are still visually separate.
// Make the background transparent to not interfere with the background overlay in placeholder-style() pseudo element
@@ -225,7 +223,7 @@
// By painting the borders here, we enable them to be replaced by the Border control.
@include placeholder-style();
- overflow: hidden;
+ overflow: auto;
}
// Position the spinner.
From f5d2c1553a8cff4e24eadc78bba6e4327f240a32 Mon Sep 17 00:00:00 2001
From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com>
Date: Mon, 11 Sep 2023 17:52:39 +0200
Subject: [PATCH 13/42] Search block: switch interactivity to the Interactivity
API (#53343)
* Fist commit
* Come back to default style, as we updated wp-class directive
* Remove not needed test
* Add interactions
* Use `wp_store` to handle ARIA label
We now use WP Store to handle ARIA labels for button functionality, doing away with hard-coding labels. The labels now adjust based on the state of the search field, providing more explicit instructions for users.
Co-authored-by: David Arenas
* Move `FORM` directives directly to the HTML
* Move `aria-label` directive inside conditional
* Change context variable name
* Add search button directives
* Add input directives and needed actions
* Remove old functions
* Load the search directives only if using Gutenberg
* Change PHP formatting
* Fix PHP coding standard issues
* Remove trailing comma
* Don't submit form when input is closed
* Focus button when closed with ESC key
* Add selector in SSR
* Format PHP
* Fix typo
Co-authored-by: Michal
* Add comment for `$open_by_default` variable
* Remove extra space
* Rename search block function
* Add `supports.interactivity`
* Remove old files and
* Add comment to manual SSR for core
* Remove wp_store
* Use `null` instead of `undefined
Co-authored-by: Luis Herranz
* Remove unnecessary conditional
Co-authored-by: Luis Herranz
---------
Co-authored-by: Michal Czaplinski
Co-authored-by: David Arenas
Co-authored-by: Mario Santos
Co-authored-by: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com>
Co-authored-by: Luis Herranz
---
docs/reference-guides/core-blocks.md | 2 +-
packages/block-library/src/search/block.json | 1 +
packages/block-library/src/search/index.php | 30 ++-
packages/block-library/src/search/view.js | 239 ++++++-------------
4 files changed, 99 insertions(+), 173 deletions(-)
diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 0f1e9da1f21799..6e66df88bbac82 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -759,7 +759,7 @@ Help visitors find your content. ([Source](https://github.com/WordPress/gutenber
- **Name:** core/search
- **Category:** widgets
-- **Supports:** align (center, left, right), color (background, gradients, text), typography (fontSize, lineHeight), ~~html~~
+- **Supports:** align (center, left, right), color (background, gradients, text), interactivity, typography (fontSize, lineHeight), ~~html~~
- **Attributes:** buttonBehavior, buttonPosition, buttonText, buttonUseIcon, isSearchFieldHidden, label, placeholder, query, showLabel, width, widthUnit
## Separator
diff --git a/packages/block-library/src/search/block.json b/packages/block-library/src/search/block.json
index b2873bfa8e5729..5669a9089d0e03 100644
--- a/packages/block-library/src/search/block.json
+++ b/packages/block-library/src/search/block.json
@@ -62,6 +62,7 @@
"text": true
}
},
+ "interactivity": true,
"typography": {
"__experimentalSkipSerialization": true,
"__experimentalSelector": ".wp-block-search__label, .wp-block-search__input, .wp-block-search__button",
diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php
index 670ceb0eb66c54..da04a0478e3069 100644
--- a/packages/block-library/src/search/index.php
+++ b/packages/block-library/src/search/index.php
@@ -46,6 +46,9 @@ function render_block_core_search( $attributes, $content, $block ) {
'button-inside' === $attributes['buttonPosition'];
// Border color classes need to be applied to the elements that have a border color.
$border_color_classes = get_border_color_classes_for_block_core_search( $attributes );
+ // This variable is a constant and its value is always false at this moment.
+ // It is defined this way because some values depend on it, in case it changes in the future.
+ $open_by_default = 'false';
$label_inner_html = empty( $attributes['label'] ) ? __( 'Search' ) : wp_kses_post( $attributes['label'] );
$label = new WP_HTML_Tag_Processor( sprintf( '%2$s ', $inline_styles['label'], $label_inner_html ) );
@@ -77,6 +80,9 @@ function render_block_core_search( $attributes, $content, $block ) {
$is_expandable_searchfield = 'button-only' === $button_position && 'expand-searchfield' === $button_behavior;
if ( $is_expandable_searchfield ) {
+ $input->set_attribute( 'data-wp-bind--aria-hidden', '!context.core.search.isSearchInputVisible' );
+ $input->set_attribute( 'data-wp-bind--tabindex', 'selectors.core.search.tabindex' );
+ // Adding these attributes manually is needed until the Interactivity API SSR logic is added to core.
$input->set_attribute( 'aria-hidden', 'true' );
$input->set_attribute( 'tabindex', '-1' );
}
@@ -139,11 +145,16 @@ function render_block_core_search( $attributes, $content, $block ) {
if ( $button->next_tag() ) {
$button->add_class( implode( ' ', $button_classes ) );
if ( 'expand-searchfield' === $attributes['buttonBehavior'] && 'button-only' === $attributes['buttonPosition'] ) {
+ $button->set_attribute( 'data-wp-bind--aria-label', 'selectors.core.search.ariaLabel' );
+ $button->set_attribute( 'data-wp-bind--aria-controls', 'selectors.core.search.ariaControls' );
+ $button->set_attribute( 'data-wp-bind--aria-expanded', 'context.core.search.isSearchInputVisible' );
+ $button->set_attribute( 'data-wp-bind--type', 'selectors.core.search.type' );
+ $button->set_attribute( 'data-wp-on--click', 'actions.core.search.openSearchInput' );
+ // Adding these attributes manually is needed until the Interactivity API SSR logic is added to core.
$button->set_attribute( 'aria-label', __( 'Expand search field' ) );
- $button->set_attribute( 'data-toggled-aria-label', __( 'Submit Search' ) );
$button->set_attribute( 'aria-controls', 'wp-block-search__input-' . $input_id );
$button->set_attribute( 'aria-expanded', 'false' );
- $button->set_attribute( 'type', 'button' ); // Will be set to submit after clicking.
+ $button->set_attribute( 'type', 'button' );
} else {
$button->set_attribute( 'aria-label', wp_strip_all_tags( $attributes['buttonText'] ) );
}
@@ -160,11 +171,24 @@ function render_block_core_search( $attributes, $content, $block ) {
$wrapper_attributes = get_block_wrapper_attributes(
array( 'class' => $classnames )
);
+ $form_directives = '';
+ if ( $is_expandable_searchfield ) {
+ $aria_label_expanded = __( 'Submit Search' );
+ $aria_label_collapsed = __( 'Expand search field' );
+ $form_directives = '
+ data-wp-interactive
+ data-wp-context=\'{ "core": { "search": { "isSearchInputVisible": ' . $open_by_default . ', "inputId": "' . $input_id . '", "ariaLabelExpanded": "' . $aria_label_expanded . '", "ariaLabelCollapsed": "' . $aria_label_collapsed . '" } } }\'
+ data-wp-class--wp-block-search__searchfield-hidden="!context.core.search.isSearchInputVisible"
+ data-wp-on--keydown="actions.core.search.handleSearchKeydown"
+ data-wp-on--focusout="actions.core.search.handleSearchFocusout"
+ ';
+ };
return sprintf(
- '',
+ '',
esc_url( home_url( '/' ) ),
$wrapper_attributes,
+ $form_directives,
$label . $field_markup
);
}
diff --git a/packages/block-library/src/search/view.js b/packages/block-library/src/search/view.js
index 5aaf1dd1ef3add..d99dfc5696ccbb 100644
--- a/packages/block-library/src/search/view.js
+++ b/packages/block-library/src/search/view.js
@@ -1,172 +1,73 @@
-/*eslint-env browser*/
-
-/** @type {?HTMLFormElement} */
-let expandedSearchBlock = null;
-
-const hiddenClass = 'wp-block-search__searchfield-hidden';
-
-/**
- * Toggles aria-label with data-toggled-aria-label.
- *
- * @param {HTMLElement} element
- */
-function toggleAriaLabel( element ) {
- if ( ! ( 'toggledAriaLabel' in element.dataset ) ) {
- throw new Error( 'Element lacks toggledAriaLabel in dataset.' );
- }
-
- const ariaLabel = element.dataset.toggledAriaLabel;
- element.dataset.toggledAriaLabel = element.ariaLabel;
- element.ariaLabel = ariaLabel;
-}
-
-/**
- * Gets search input.
- *
- * @param {HTMLFormElement} block Search block.
- * @return {HTMLInputElement} Search input.
- */
-function getSearchInput( block ) {
- return block.querySelector( '.wp-block-search__input' );
-}
-
-/**
- * Gets search button.
- *
- * @param {HTMLFormElement} block Search block.
- * @return {HTMLButtonElement} Search button.
- */
-function getSearchButton( block ) {
- return block.querySelector( '.wp-block-search__button' );
-}
-
-/**
- * Handles keydown event to collapse an expanded Search block (when pressing Escape key).
- *
- * @param {KeyboardEvent} event
- */
-function handleKeydownEvent( event ) {
- if ( ! expandedSearchBlock ) {
- // In case the event listener wasn't removed in time.
- return;
- }
-
- if ( event.key === 'Escape' ) {
- const block = expandedSearchBlock; // This is nullified by collapseExpandedSearchBlock().
- collapseExpandedSearchBlock();
- getSearchButton( block ).focus();
- }
-}
-
-/**
- * Handles keyup event to collapse an expanded Search block (e.g. when tabbing out of expanded Search block).
- *
- * @param {KeyboardEvent} event
- */
-function handleKeyupEvent( event ) {
- if ( ! expandedSearchBlock ) {
- // In case the event listener wasn't removed in time.
- return;
- }
-
- if ( event.target.closest( '.wp-block-search' ) !== expandedSearchBlock ) {
- collapseExpandedSearchBlock();
- }
-}
-
-/**
- * Expands search block.
- *
- * Inverse of what is done in collapseExpandedSearchBlock().
- *
- * @param {HTMLFormElement} block Search block.
- */
-function expandSearchBlock( block ) {
- // Make sure only one is open at a time.
- if ( expandedSearchBlock ) {
- collapseExpandedSearchBlock();
- }
-
- const searchField = getSearchInput( block );
- const searchButton = getSearchButton( block );
-
- searchButton.type = 'submit';
- searchField.ariaHidden = 'false';
- searchField.tabIndex = 0;
- searchButton.ariaExpanded = 'true';
- searchButton.removeAttribute( 'aria-controls' ); // Note: Seemingly not reflected with searchButton.ariaControls.
- toggleAriaLabel( searchButton );
- block.classList.remove( hiddenClass );
-
- searchField.focus(); // Note that Chrome seems to do this automatically.
-
- // The following two must be inverse of what is done in collapseExpandedSearchBlock().
- document.addEventListener( 'keydown', handleKeydownEvent, {
- passive: true,
- } );
- document.addEventListener( 'keyup', handleKeyupEvent, {
- passive: true,
- } );
-
- expandedSearchBlock = block;
-}
-
/**
- * Collapses the expanded search block.
- *
- * Inverse of what is done in expandSearchBlock().
+ * WordPress dependencies
*/
-function collapseExpandedSearchBlock() {
- if ( ! expandedSearchBlock ) {
- throw new Error( 'Expected expandedSearchBlock to be defined.' );
- }
- const block = expandedSearchBlock;
- const searchField = getSearchInput( block );
- const searchButton = getSearchButton( block );
-
- searchButton.type = 'button';
- searchField.ariaHidden = 'true';
- searchField.tabIndex = -1;
- searchButton.ariaExpanded = 'false';
- searchButton.setAttribute( 'aria-controls', searchField.id ); // Note: Seemingly not reflected with searchButton.ariaControls.
- toggleAriaLabel( searchButton );
- block.classList.add( hiddenClass );
-
- // The following two must be inverse of what is done in expandSearchBlock().
- document.removeEventListener( 'keydown', handleKeydownEvent, {
- passive: true,
- } );
- document.removeEventListener( 'keyup', handleKeyupEvent, {
- passive: true,
- } );
-
- expandedSearchBlock = null;
-}
-
-// Listen for click events anywhere on the document so this script can be loaded asynchronously in the head.
-document.addEventListener(
- 'click',
- ( event ) => {
- // Get the ancestor expandable Search block of the clicked element.
- const block = event.target.closest(
- '.wp-block-search__button-behavior-expand'
- );
-
- /*
- * If there is already an expanded search block and either the current click was not for a Search block or it was
- * for another block, then collapse the currently-expanded block.
- */
- if ( expandedSearchBlock && block !== expandedSearchBlock ) {
- collapseExpandedSearchBlock();
- }
-
- // If the click was on or inside a collapsed Search block, expand it.
- if (
- block instanceof HTMLFormElement &&
- block.classList.contains( hiddenClass )
- ) {
- expandSearchBlock( block );
- }
+import { store as wpStore } from '@wordpress/interactivity';
+
+wpStore( {
+ selectors: {
+ core: {
+ search: {
+ ariaLabel: ( { context } ) => {
+ const { ariaLabelCollapsed, ariaLabelExpanded } =
+ context.core.search;
+ return context.core.search.isSearchInputVisible
+ ? ariaLabelExpanded
+ : ariaLabelCollapsed;
+ },
+ ariaControls: ( { context } ) => {
+ return context.core.search.isSearchInputVisible
+ ? null
+ : context.core.search.inputId;
+ },
+ type: ( { context } ) => {
+ return context.core.search.isSearchInputVisible
+ ? 'submit'
+ : 'button';
+ },
+ tabindex: ( { context } ) => {
+ return context.core.search.isSearchInputVisible
+ ? '0'
+ : '-1';
+ },
+ },
+ },
+ },
+ actions: {
+ core: {
+ search: {
+ openSearchInput: ( { context, event, ref } ) => {
+ if ( ! context.core.search.isSearchInputVisible ) {
+ event.preventDefault();
+ context.core.search.isSearchInputVisible = true;
+ ref.parentElement.querySelector( 'input' ).focus();
+ }
+ },
+ closeSearchInput: ( { context } ) => {
+ context.core.search.isSearchInputVisible = false;
+ },
+ handleSearchKeydown: ( store ) => {
+ const { actions, event, ref } = store;
+ // If Escape close the menu.
+ if ( event?.key === 'Escape' ) {
+ actions.core.search.closeSearchInput( store );
+ ref.querySelector( 'button' ).focus();
+ }
+ },
+ handleSearchFocusout: ( store ) => {
+ const { actions, event, ref } = store;
+ // If focus is outside search form, and in the document, close menu
+ // event.target === The element losing focus
+ // event.relatedTarget === The element receiving focus (if any)
+ // When focusout is outside the document,
+ // `window.document.activeElement` doesn't change.
+ if (
+ ! ref.contains( event.relatedTarget ) &&
+ event.target !== window.document.activeElement
+ ) {
+ actions.core.search.closeSearchInput( store );
+ }
+ },
+ },
+ },
},
- { passive: true }
-);
+} );
From f602d228550113033837451864b1111e0d5fbed8 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 11 Sep 2023 18:55:06 +0300
Subject: [PATCH 14/42] Remove accidental addition of perf test results
(#54355)
---
.../performance/site-editor.test.results.json | 60 -------------------
1 file changed, 60 deletions(-)
delete mode 100644 packages/e2e-tests/specs/performance/site-editor.test.results.json
diff --git a/packages/e2e-tests/specs/performance/site-editor.test.results.json b/packages/e2e-tests/specs/performance/site-editor.test.results.json
deleted file mode 100644
index a043c552f12d3f..00000000000000
--- a/packages/e2e-tests/specs/performance/site-editor.test.results.json
+++ /dev/null
@@ -1,60 +0,0 @@
-{
- "serverResponse": [
- 409.40000009536743, 405.59999990463257, 410.09999990463257
- ],
- "firstPaint": [ 438.59999990463257, 447.3999996185303, 449.59999990463257 ],
- "domContentLoaded": [ 676, 690.0999999046326, 693 ],
- "loaded": [ 1405.6999998092651, 1400.3999996185303, 1425.9000000953674 ],
- "firstContentfulPaint": [
- 903.2999997138977, 921.0999999046326, 925.2999997138977
- ],
- "firstBlock": [
- 3166.7999997138977, 3206.5999999046326, 3238.4000000953674
- ],
- "type": [
- 81.45900000000002, 37.088, 36.051, 38.596000000000004, 49.931, 40.322,
- 38.99999999999999, 34.235, 33.608999999999995, 32.88399999999999, 30.44,
- 37.113, 31.534999999999997, 33.792, 36.942, 35.251000000000005, 33.722,
- 33.471999999999994, 35.26499999999999, 29.682, 30.173,
- 30.674999999999997, 35.668000000000006, 38.278, 37.62, 37.562, 38.091,
- 32.237, 28.119999999999997, 31.342, 39.89, 37.443, 37.761, 40.262,
- 37.922, 30.727, 30.955000000000002, 36.53000000000001, 32.293, 37.299,
- 38.55800000000001, 39.85699999999999, 33.721999999999994, 30.139,
- 29.294, 31.016, 35.7, 36.839, 31.061000000000003, 29.540000000000003,
- 48.998999999999995, 35.423, 33.650000000000006, 29.404999999999998,
- 32.744, 30.584999999999997, 30.705, 31.873, 28.907, 30.516, 30.882,
- 29.257, 29.794, 31.150000000000002, 32.095, 31.066000000000003, 32.872,
- 31.894, 31.331, 31.796, 31.675, 30.427999999999997, 30.872, 30.974,
- 32.707, 31.849999999999998, 28.935, 28.441000000000003,
- 30.566000000000003, 29.014, 33.158, 32.272, 28.990000000000002, 28.76,
- 28.967000000000002, 29.418, 28.503, 31.255000000000003, 28.703,
- 30.369000000000003, 34.910000000000004, 31.03, 28.523,
- 32.361999999999995, 33.870000000000005, 30.11, 30.944000000000003,
- 28.601, 30.572999999999997, 33.216, 30.822, 28.892000000000003,
- 32.95099999999999, 31.228, 28.251, 34.89, 30.131000000000004, 29.395,
- 31.557000000000002, 28.137, 32.051, 38.242, 36.382999999999996, 35.037,
- 36.2, 31.717999999999996, 28.927999999999997, 32.540000000000006,
- 35.448, 28.292, 35.059999999999995, 31.345000000000002, 36.122, 31.69,
- 28.492, 29.308, 30.793000000000003, 28.784000000000002,
- 28.275999999999996, 36.577999999999996, 30.220000000000002, 35.832,
- 31.192, 36.102999999999994, 30.733999999999998, 30.574,
- 35.455999999999996, 29.963, 37.967, 29.323999999999998, 36.643,
- 31.200000000000003, 36.864999999999995, 32.344, 30.321, 29.214, 28.627,
- 29.71, 29.006, 36.067, 29.583, 29.562, 37.795, 30.166999999999998,
- 30.811999999999998, 33.319, 32.939, 39.233999999999995, 28.856,
- 34.81700000000001, 30.324, 33.611000000000004, 33.707,
- 30.191000000000003, 29.191, 29.23, 30.715, 29.281, 28.168,
- 33.449000000000005, 36.36600000000001, 29.086, 30.589, 29.13, 28.789,
- 29.156000000000002, 43.327, 34.439, 28.777, 30.586, 28.973000000000003,
- 30.026, 40.023, 30.203, 28.328000000000003, 30.825000000000003, 29.739,
- 31.504, 43.708000000000006, 29.296999999999997, 32.294, 31.733, 30.44,
- 28.879, 30.349999999999998, 29.466, 29.302999999999997, 30,
- 29.468999999999998, 28.740000000000002
- ],
- "typeContainer": [],
- "focus": [],
- "inserterOpen": [],
- "inserterHover": [],
- "inserterSearch": [],
- "listViewOpen": []
-}
From d31f96af531b89e00e4952c2f98a105d21b5dde1 Mon Sep 17 00:00:00 2001
From: Rich Tabor
Date: Mon, 11 Sep 2023 12:50:30 -0400
Subject: [PATCH 15/42] Solid accent color selection in command palette
(#54318)
---
packages/commands/src/components/style.scss | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/commands/src/components/style.scss b/packages/commands/src/components/style.scss
index 8466fabe9dd799..aeb818507f0730 100644
--- a/packages/commands/src/components/style.scss
+++ b/packages/commands/src/components/style.scss
@@ -78,11 +78,11 @@
&[aria-selected="true"],
&:active {
- background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
- color: var(--wp-admin-theme-color);
+ background: var(--wp-admin-theme-color);
+ color: $white;
svg {
- fill: var(--wp-admin-theme-color);
+ fill: $white;
}
}
From 0246dab961147d51cee3498b3d5d913355eecdac Mon Sep 17 00:00:00 2001
From: Gerardo Pacheco
Date: Mon, 11 Sep 2023 20:44:29 +0200
Subject: [PATCH 16/42] Mobile Release v1.103.2 (#54353)
* Release script: Update react-native-editor version to 1.103.0
* Release script: Update with changes from 'npm run core preios'
* Update Changelog
* Update package-lock.json
* Release script: Update react-native-editor version to 1.103.1
* Release script: Update with changes from 'npm run core preios'
* Fix long-press gestures not working in `RichText` component [Android] (#54213)
* Use different touchable components in RichText based on platform
* Update mobile test snapshots
* Update `package-lock.json` file
* Update `react-native-editor` changelog
* Release script: Update react-native-editor version to 1.103.2
* Release script: Update with changes from 'npm run core preios'
* [RNMobile] Fix issue with missing characters in Add Media placeholder button (#54281)
* Fix media placeholder text issue
* Update CHANGELOG
* Update Gallery block media placeholder text
* Update letter case for File block button
* Update package-lock.json
---------
Co-authored-by: Carlos Garcia
Co-authored-by: Derek Blank
---
package-lock.json | 6 +++---
packages/react-native-aztec/package.json | 2 +-
packages/react-native-bridge/package.json | 2 +-
packages/react-native-editor/CHANGELOG.md | 2 ++
packages/react-native-editor/ios/Podfile.lock | 8 ++++----
packages/react-native-editor/package.json | 2 +-
6 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 340130affbd181..530c3d48d08708 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -56198,7 +56198,7 @@
},
"packages/react-native-aztec": {
"name": "@wordpress/react-native-aztec",
- "version": "1.103.1",
+ "version": "1.103.2",
"license": "GPL-2.0-or-later",
"dependencies": {
"@wordpress/element": "file:../element",
@@ -56211,7 +56211,7 @@
},
"packages/react-native-bridge": {
"name": "@wordpress/react-native-bridge",
- "version": "1.103.1",
+ "version": "1.103.2",
"license": "GPL-2.0-or-later",
"dependencies": {
"@wordpress/react-native-aztec": "file:../react-native-aztec"
@@ -56222,7 +56222,7 @@
},
"packages/react-native-editor": {
"name": "@wordpress/react-native-editor",
- "version": "1.103.1",
+ "version": "1.103.2",
"hasInstallScript": true,
"license": "GPL-2.0-or-later",
"dependencies": {
diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json
index 2c63816b661d91..d0879be9a1d770 100644
--- a/packages/react-native-aztec/package.json
+++ b/packages/react-native-aztec/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/react-native-aztec",
- "version": "1.103.1",
+ "version": "1.103.2",
"description": "Aztec view for react-native.",
"private": true,
"author": "The WordPress Contributors",
diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json
index 203bcf11e60c5d..a5b817cc115e36 100644
--- a/packages/react-native-bridge/package.json
+++ b/packages/react-native-bridge/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/react-native-bridge",
- "version": "1.103.1",
+ "version": "1.103.2",
"description": "Native bridge library used to integrate the block editor into a native App.",
"private": true,
"author": "The WordPress Contributors",
diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md
index 031c77b8ee8ed7..1f1d4cba4dd991 100644
--- a/packages/react-native-editor/CHANGELOG.md
+++ b/packages/react-native-editor/CHANGELOG.md
@@ -11,6 +11,8 @@ For each user feature we should also add a importance categorization label to i
## Unreleased
- [*] Fix the obscurred "Insert from URL" input for media blocks when using a device in landscape orientation. [#54096]
+
+## 1.103.2
- [*] Fix issue with missing characters in Add Media placeholder button [#54281]
## 1.103.1
diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock
index c8ad1e5b6958dc..2b67491e5b14fd 100644
--- a/packages/react-native-editor/ios/Podfile.lock
+++ b/packages/react-native-editor/ios/Podfile.lock
@@ -13,7 +13,7 @@ PODS:
- ReactCommon/turbomodule/core (= 0.71.11)
- fmt (6.2.1)
- glog (0.3.5)
- - Gutenberg (1.103.1):
+ - Gutenberg (1.103.2):
- React-Core (= 0.71.11)
- React-CoreModules (= 0.71.11)
- React-RCTImage (= 0.71.11)
@@ -394,7 +394,7 @@ PODS:
- React-RCTImage
- RNSVG (13.9.0):
- React-Core
- - RNTAztecView (1.103.1):
+ - RNTAztecView (1.103.2):
- React-Core
- WordPress-Aztec-iOS (~> 1.19.8)
- SDWebImage (5.11.1):
@@ -577,7 +577,7 @@ SPEC CHECKSUMS:
FBReactNativeSpec: f07662560742d82a5b73cee116c70b0b49bcc220
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
- Gutenberg: 55a519e153b2e1f30974ad3bbe04bbfacbf7f88b
+ Gutenberg: fe1145de4daa0d40dd21f838f449305f54d408b7
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
RCTRequired: f6187ec763637e6a57f5728dd9a3bdabc6d6b4e0
@@ -620,7 +620,7 @@ SPEC CHECKSUMS:
RNReanimated: df2567658c01135f9ff4709d372675bcb9fd1d83
RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789
RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315
- RNTAztecView: 3711b6ce7e5142f5d3db825cc06f8b9ed5c16198
+ RNTAztecView: 44c6114d1f76fb4bb6f325ee5289ed7a08f077f3
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504
diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json
index 69da3937d0da45..fb614dfaa11f28 100644
--- a/packages/react-native-editor/package.json
+++ b/packages/react-native-editor/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/react-native-editor",
- "version": "1.103.1",
+ "version": "1.103.2",
"description": "Mobile WordPress gutenberg editor.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
From ef5bd83b643be2da33e7ba6cc453a81c233bbbd4 Mon Sep 17 00:00:00 2001
From: Clement Boirie
Date: Mon, 11 Sep 2023 21:03:56 +0200
Subject: [PATCH 17/42] Buttons Block: Show inserter if button have variations
(#53498) (#53783)
* Buttons Block: Show inserter if button have variations (#53498)
* Combine `useSelect` variations with the existing one
* Add comment about the custom check for the `directInsert` option value that should be handled in the `Inserter`
---
packages/block-library/src/buttons/edit.js | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/packages/block-library/src/buttons/edit.js b/packages/block-library/src/buttons/edit.js
index c901655dd42631..dce160dfb2fea8 100644
--- a/packages/block-library/src/buttons/edit.js
+++ b/packages/block-library/src/buttons/edit.js
@@ -12,6 +12,7 @@ import {
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
+import { store as blocksStore } from '@wordpress/blocks';
/**
* Internal dependencies
@@ -42,17 +43,26 @@ function ButtonsEdit( { attributes, className } ) {
'has-custom-font-size': fontSize || style?.typography?.fontSize,
} ),
} );
- const preferredStyle = useSelect( ( select ) => {
+ const { preferredStyle, hasButtonVariations } = useSelect( ( select ) => {
const preferredStyleVariations =
select( blockEditorStore ).getSettings()
.__experimentalPreferredStyleVariations;
- return preferredStyleVariations?.value?.[ buttonBlockName ];
+ const buttonVariations = select( blocksStore ).getBlockVariations(
+ buttonBlockName,
+ 'inserter'
+ );
+ return {
+ preferredStyle:
+ preferredStyleVariations?.value?.[ buttonBlockName ],
+ hasButtonVariations: buttonVariations.length > 0,
+ };
}, [] );
const innerBlocksProps = useInnerBlocksProps( blockProps, {
allowedBlocks: ALLOWED_BLOCKS,
defaultBlock: DEFAULT_BLOCK,
- directInsert: true,
+ // This check should be handled by the `Inserter` internally to be consistent across all blocks that use it.
+ directInsert: ! hasButtonVariations,
template: [
[
buttonBlockName,
From b18a270ae4b5d71cc0d880d40d21b5163686cd92 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 11 Sep 2023 22:36:38 +0300
Subject: [PATCH 18/42] Iframe: skip scoping styles (#46752)
---
.../src/components/block-canvas/index.js | 5 ++++-
.../src/components/block-list/content.scss | 1 -
.../src/components/editor-styles/index.js | 17 +++++++----------
packages/block-library/src/reset.scss | 2 +-
.../src/components/block-editor/style.scss | 1 +
5 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/packages/block-editor/src/components/block-canvas/index.js b/packages/block-editor/src/components/block-canvas/index.js
index 14910e3007768f..70d6bbad99e522 100644
--- a/packages/block-editor/src/components/block-canvas/index.js
+++ b/packages/block-editor/src/components/block-canvas/index.js
@@ -34,7 +34,10 @@ export function ExperimentalBlockCanvas( {
if ( ! shouldIframe ) {
return (
<>
-
+
{
if ( ! node ) {
@@ -28,9 +27,7 @@ function useDarkThemeBodyClassName( styles ) {
const { ownerDocument } = node;
const { defaultView, body } = ownerDocument;
- const canvas = ownerDocument.querySelector(
- EDITOR_STYLES_SELECTOR
- );
+ const canvas = scope ? ownerDocument.querySelector( scope ) : body;
let backgroundColor;
@@ -63,11 +60,11 @@ function useDarkThemeBodyClassName( styles ) {
body.classList.add( 'is-dark-theme' );
}
},
- [ styles ]
+ [ styles, scope ]
);
}
-export default function EditorStyles( { styles } ) {
+export default function EditorStyles( { styles, scope } ) {
const stylesArray = useMemo(
() => Object.values( styles ?? [] ),
[ styles ]
@@ -76,9 +73,9 @@ export default function EditorStyles( { styles } ) {
() =>
transformStyles(
stylesArray.filter( ( style ) => style?.css ),
- EDITOR_STYLES_SELECTOR
+ scope
),
- [ stylesArray ]
+ [ stylesArray, scope ]
);
const transformedSvgs = useMemo(
@@ -94,7 +91,7 @@ export default function EditorStyles( { styles } ) {
<>
{ /* Use an empty style element to have a document reference,
but this could be any element. */ }
-
+
{ transformedStyles.map( ( css, index ) => (
) ) }
diff --git a/packages/block-library/src/reset.scss b/packages/block-library/src/reset.scss
index 6c3cc7b1d45be8..cf5e5a1596df59 100644
--- a/packages/block-library/src/reset.scss
+++ b/packages/block-library/src/reset.scss
@@ -7,7 +7,7 @@
// We use :where to keep specificity minimal.
// https://css-tricks.com/almanac/selectors/w/where/
-html :where(.editor-styles-wrapper) {
+:where(.editor-styles-wrapper) {
/**
* The following styles revert to the browser defaults overriding the WPAdmin styles.
* This is only needed while the block editor is not being loaded in an iframe.
diff --git a/packages/edit-site/src/components/block-editor/style.scss b/packages/edit-site/src/components/block-editor/style.scss
index dce224998c0c07..15fc8e180f818f 100644
--- a/packages/edit-site/src/components/block-editor/style.scss
+++ b/packages/edit-site/src/components/block-editor/style.scss
@@ -31,6 +31,7 @@
display: block;
width: 100%;
height: 100%;
+ background: $white;
}
.edit-site-visual-editor__editor-canvas {
From 8f6386552544082203f058ab5cba52bd428c09d7 Mon Sep 17 00:00:00 2001
From: Glen Davies
Date: Tue, 12 Sep 2023 09:03:41 +1200
Subject: [PATCH 19/42] Remove experimental prefixes from pattern package
methods as they are not exported (#54338)
---
packages/patterns/src/components/create-pattern-modal.js | 2 +-
.../patterns/src/components/patterns-manage-button.js | 7 ++++---
packages/patterns/src/store/actions.js | 8 ++++----
packages/patterns/src/store/selectors.js | 2 +-
4 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js
index a6aa4007764fcc..5623a04fcba611 100644
--- a/packages/patterns/src/components/create-pattern-modal.js
+++ b/packages/patterns/src/components/create-pattern-modal.js
@@ -35,7 +35,7 @@ export default function CreatePatternModal( {
} ) {
const [ syncType, setSyncType ] = useState( SYNC_TYPES.full );
const [ title, setTitle ] = useState( '' );
- const { __experimentalCreatePattern: createPattern } = useDispatch( store );
+ const { createPattern } = useDispatch( store );
const { createErrorNotice } = useDispatch( noticesStore );
const onCreate = useCallback(
diff --git a/packages/patterns/src/components/patterns-manage-button.js b/packages/patterns/src/components/patterns-manage-button.js
index bfa36521a28c1f..eae307f1838dec 100644
--- a/packages/patterns/src/components/patterns-manage-button.js
+++ b/packages/patterns/src/components/patterns-manage-button.js
@@ -51,8 +51,7 @@ function PatternsManageButton( { clientId } ) {
[ clientId ]
);
- const { __experimentalConvertSyncedPatternToStatic: convertBlockToStatic } =
- useDispatch( editorStore );
+ const { convertSyncedPatternToStatic } = useDispatch( editorStore );
if ( ! isVisible ) {
return null;
@@ -64,7 +63,9 @@ function PatternsManageButton( { clientId } ) {
{ __( 'Manage patterns' ) }
{ canRemove && (
- convertBlockToStatic( clientId ) }>
+ convertSyncedPatternToStatic( clientId ) }
+ >
{ innerBlockCount > 1
? __( 'Detach patterns' )
: __( 'Detach pattern' ) }
diff --git a/packages/patterns/src/store/actions.js b/packages/patterns/src/store/actions.js
index 2128526b2b3194..0a0306c736621a 100644
--- a/packages/patterns/src/store/actions.js
+++ b/packages/patterns/src/store/actions.js
@@ -13,7 +13,7 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
* @param {'full'|'unsynced'} syncType They way block is synced, 'full' or 'unsynced'.
* @param {string[]|undefined} clientIds Optional client IDs of blocks to convert to pattern.
*/
-export const __experimentalCreatePattern =
+export const createPattern =
( title, syncType, clientIds ) =>
async ( { registry, dispatch } ) => {
const meta =
@@ -50,7 +50,7 @@ export const __experimentalCreatePattern =
registry
.dispatch( blockEditorStore )
.replaceBlocks( clientIds, newBlock );
- dispatch.__experimentalSetEditingPattern( newBlock.clientId, true );
+ dispatch.setEditingPattern( newBlock.clientId, true );
return updatedRecord;
};
@@ -59,7 +59,7 @@ export const __experimentalCreatePattern =
*
* @param {string} clientId The client ID of the block to attach.
*/
-export const __experimentalConvertSyncedPatternToStatic =
+export const convertSyncedPatternToStatic =
( clientId ) =>
( { registry } ) => {
const oldBlock = registry
@@ -90,7 +90,7 @@ export const __experimentalConvertSyncedPatternToStatic =
* @param {boolean} isEditing Whether the block should be in editing state.
* @return {Object} Action descriptor.
*/
-export function __experimentalSetEditingPattern( clientId, isEditing ) {
+export function setEditingPattern( clientId, isEditing ) {
return {
type: 'SET_EDITING_PATTERN',
clientId,
diff --git a/packages/patterns/src/store/selectors.js b/packages/patterns/src/store/selectors.js
index 3089737c856c86..63a4d5ff9dfbdf 100644
--- a/packages/patterns/src/store/selectors.js
+++ b/packages/patterns/src/store/selectors.js
@@ -5,6 +5,6 @@
* @param {number} clientId the clientID of the block.
* @return {boolean} Whether the pattern is in the editing state.
*/
-export function __experimentalIsEditingPattern( state, clientId ) {
+export function isEditingPattern( state, clientId ) {
return state.isEditingPattern[ clientId ];
}
From 3704da47147a5f426f930a09e1351f03f0271cd0 Mon Sep 17 00:00:00 2001
From: Brooke <35543432+brookewp@users.noreply.github.com>
Date: Mon, 11 Sep 2023 19:56:23 -0700
Subject: [PATCH 20/42] Navigation Link: restore tooltip (#54263)
---
.../block-library/src/navigation-link/edit.js | 57 +++++++------------
.../src/navigation-link/editor.scss | 8 ---
2 files changed, 21 insertions(+), 44 deletions(-)
diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js
index df7ca36d7272af..4a902871abdee0 100644
--- a/packages/block-library/src/navigation-link/edit.js
+++ b/packages/block-library/src/navigation-link/edit.js
@@ -486,13 +486,8 @@ export default function NavigationLinkEdit( {
{ /* eslint-enable */ }
{ ! url ? (
-
- <>
- { missingText }
-
- { tooltipText }
-
- >
+
+ { missingText }
) : (
@@ -548,35 +543,25 @@ export default function NavigationLinkEdit( {
isDraft ||
isLabelFieldFocused ) && (
-
- <>
-
- {
- // Some attributes are stored in an escaped form. It's a legacy issue.
- // Ideally they would be stored in a raw, unescaped form.
- // Unescape is used here to "recover" the escaped characters
- // so they display without encoding.
- // See `updateAttributes` for more details.
- `${ decodeEntities(
- label
- ) } ${
- isInvalid || isDraft
- ? placeholderText
- : ''
- }`.trim()
- }
-
-
- { tooltipText }
-
- >
+
+
+ {
+ // Some attributes are stored in an escaped form. It's a legacy issue.
+ // Ideally they would be stored in a raw, unescaped form.
+ // Unescape is used here to "recover" the escaped characters
+ // so they display without encoding.
+ // See `updateAttributes` for more details.
+ `${ decodeEntities( label ) } ${
+ isInvalid || isDraft
+ ? placeholderText
+ : ''
+ }`.trim()
+ }
+
) }
diff --git a/packages/block-library/src/navigation-link/editor.scss b/packages/block-library/src/navigation-link/editor.scss
index 327f89c58ae3ab..6d1dc32e5310b5 100644
--- a/packages/block-library/src/navigation-link/editor.scss
+++ b/packages/block-library/src/navigation-link/editor.scss
@@ -67,14 +67,6 @@
color: #000;
}
-.wp-block-navigation-link__missing_text-tooltip {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
-}
/**
* Menu item setup state. Is shown when a menu item has no URL configured.
*/
From bd52276857a70d4ea33c7947ac71247e5ab933ac Mon Sep 17 00:00:00 2001
From: Alex Stine
Date: Mon, 11 Sep 2023 22:49:31 -0500
Subject: [PATCH 21/42] Table block: Fix semantic structure for screen readers
on back-end (#54324)
* Fix table block edit structure for screen readers.
* Fix td props. Fix first cell focus.
* Move className to td.
---
packages/block-library/src/table/edit.js | 40 +++++++++++++-----------
1 file changed, 21 insertions(+), 19 deletions(-)
diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js
index 4f186f77a72918..17a9e1ecfdd5bf 100644
--- a/packages/block-library/src/table/edit.js
+++ b/packages/block-library/src/table/edit.js
@@ -349,7 +349,7 @@ function TableEdit( {
useEffect( () => {
if ( hasTableCreated ) {
tableRef?.current
- ?.querySelector( 'td[contentEditable="true"]' )
+ ?.querySelector( 'td div[contentEditable="true"]' )
?.focus();
setHasTableCreated( false );
}
@@ -414,31 +414,33 @@ function TableEdit( {
},
columnIndex
) => (
- {
- setSelectedCell( {
- sectionName: name,
- rowIndex,
- columnIndex,
- type: 'cell',
- } );
- } }
- aria-label={ cellAriaLabel[ name ] }
- placeholder={ placeholder[ name ] }
- />
+ >
+ {
+ setSelectedCell( {
+ sectionName: name,
+ rowIndex,
+ columnIndex,
+ type: 'cell',
+ } );
+ } }
+ aria-label={ cellAriaLabel[ name ] }
+ placeholder={ placeholder[ name ] }
+ />
+
)
) }
From 94aad94984d2845ecc06d765fd0edb846df9870d Mon Sep 17 00:00:00 2001
From: Brian
Date: Tue, 12 Sep 2023 06:24:37 +0200
Subject: [PATCH 22/42] Change dialogdescription for renaming group block
(#54358)
---
packages/block-editor/src/hooks/block-rename-ui.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/block-editor/src/hooks/block-rename-ui.js b/packages/block-editor/src/hooks/block-rename-ui.js
index 189090668cbb45..835a09556aed5e 100644
--- a/packages/block-editor/src/hooks/block-rename-ui.js
+++ b/packages/block-editor/src/hooks/block-rename-ui.js
@@ -71,7 +71,7 @@ function RenameModal( { blockName, originalBlockName, onClose, onSave } ) {
} }
>
- { __( 'Choose a custom name for this block.' ) }
+ { __( 'Enter a custom name for this block.' ) }
- { children( renderProps ) }
+ { children?.( renderProps ) }
>
) }
From f76d48a59bdf1f1d744fae2a2e4763b1579bcb34 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Tue, 12 Sep 2023 09:34:44 +0100
Subject: [PATCH 24/42] Extract WordPress specific styles from the BlockTools
component (#54356)
---
.../src/components/block-tools/style.scss | 98 -------------------
.../src/components/visual-editor/style.scss | 89 +++++++++++++++++
.../src/components/block-editor/style.scss | 86 ++++++++++++++++
.../style.scss | 98 +++++++++++++++++++
storybook/stories/playground/index.story.js | 39 +++++++-
5 files changed, 311 insertions(+), 99 deletions(-)
diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss
index a6b7d636f491ba..796c513d6d7c2c 100644
--- a/packages/block-editor/src/components/block-tools/style.scss
+++ b/packages/block-editor/src/components/block-tools/style.scss
@@ -89,9 +89,6 @@
* Block Toolbar when contextual.
*/
-// Base left position for the toolbar when fixed.
-@include editor-left(".block-editor-block-contextual-toolbar.is-fixed");
-
.block-editor-block-contextual-toolbar {
// Block UI appearance.
display: inline-flex;
@@ -105,11 +102,6 @@
}
&.is-fixed {
- position: sticky;
- top: 0;
- z-index: z-index(".block-editor-block-popover");
- display: block;
- width: 100%;
overflow: hidden;
.block-editor-block-toolbar {
@@ -137,51 +129,8 @@
background: linear-gradient(to right, $white, transparent);
}
- // on desktop and tablet viewports the toolbar is fixed
- // on top of interface header
- $toolbar-margin: $grid-unit-80 * 3 - 2 * $grid-unit + $grid-unit-05;
@include break-medium() {
&.is-fixed {
- // leave room for block inserter, undo and redo, list view
- margin-left: $toolbar-margin;
- // position on top of interface header
- position: fixed;
- top: $admin-bar-height;
- // Don't fill up when empty
- min-height: initial;
- // remove the border
- border-bottom: none;
- // has to be flex for collapse button to fit
- display: flex;
-
- // Mimic the height of the parent, vertically align center, and provide a max-height.
- height: $header-height;
- align-items: center;
-
- &.is-collapsed {
- width: initial;
- }
-
- &:empty {
- width: initial;
- }
-
- .is-fullscreen-mode & {
- // leave room for block inserter, undo and redo, list view
- // and some margin left
- margin-left: $grid-unit-80 * 4 - 2 * $grid-unit;
-
- top: 0;
-
- &.is-collapsed {
- width: initial;
- }
-
- &:empty {
- width: initial;
- }
- }
-
& > .block-editor-block-toolbar {
flex-grow: initial;
width: initial;
@@ -264,13 +213,6 @@
}
.show-icon-labels & {
-
- margin-left: $grid-unit-80 + 2 * $grid-unit; // inserter and margin
-
- .is-fullscreen-mode & {
- margin-left: $grid-unit * 18; // site hub, inserter and margin
- }
-
.block-editor-block-parent-selector .block-editor-block-parent-selector__button::after {
left: 0;
}
@@ -287,14 +229,6 @@
}
}
}
-
- .blocks-widgets-container & {
- margin-left: $grid-unit-80 * 2.4;
-
- &.is-collapsed {
- margin-left: $grid-unit-80 * 4.2;
- }
- }
}
&.is-fixed .block-editor-block-parent-selector {
@@ -332,38 +266,6 @@
}
}
}
-
- // on tablet viewports the toolbar is fixed
- // on top of interface header and covers the whole header
- // except for the inserter on the left
- @include break-medium() {
- &.is-fixed {
- width: calc(100% - #{$toolbar-margin});
-
- .show-icon-labels & {
- width: calc(100% + 40px - #{$toolbar-margin}); //there are no undo, redo and list view buttons
- }
-
- }
- }
-
- // on desktop viewports the toolbar is fixed
- // on top of interface header and leaves room
- // for the block inserter the publish button
- @include break-large() {
- &.is-fixed {
- width: auto;
- .show-icon-labels & {
- width: auto; //there are no undo, redo and list view buttons
- }
- }
- .is-fullscreen-mode &.is-fixed {
- // in full screen mode we need to account for
- // the combined with of the tools at the right of the header and the margin left
- // of the toolbar which includes four buttons
- width: calc(100% - 280px - #{4 * $grid-unit-80});
- }
- }
}
/**
diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss
index fa61cc9889cf9c..40043958fcaad5 100644
--- a/packages/edit-post/src/components/visual-editor/style.scss
+++ b/packages/edit-post/src/components/visual-editor/style.scss
@@ -67,3 +67,92 @@
// See also https://www.w3.org/TR/CSS22/visudet.html#the-height-property
flex-grow: 1;
}
+
+// Fixed contextual toolbar
+@include editor-left(".edit-post-visual-editor .block-editor-block-contextual-toolbar.is-fixed");
+
+.edit-post-visual-editor .block-editor-block-contextual-toolbar.is-fixed {
+ position: sticky;
+ top: 0;
+ z-index: z-index(".block-editor-block-popover");
+ display: block;
+ width: 100%;
+
+ // on desktop and tablet viewports the toolbar is fixed
+ // on top of interface header
+ $toolbar-margin: $grid-unit-80 * 3 - 2 * $grid-unit + $grid-unit-05;
+
+ @include break-medium() {
+ // leave room for block inserter, undo and redo, list view
+ margin-left: $toolbar-margin;
+ // position on top of interface header
+ position: fixed;
+ top: $admin-bar-height;
+ // Don't fill up when empty
+ min-height: initial;
+ // remove the border
+ border-bottom: none;
+ // has to be flex for collapse button to fit
+ display: flex;
+
+ // Mimic the height of the parent, vertically align center, and provide a max-height.
+ height: $header-height;
+ align-items: center;
+
+
+ // on tablet viewports the toolbar is fixed
+ // on top of interface header and covers the whole header
+ // except for the inserter on the left
+ width: calc(100% - #{$toolbar-margin});
+
+ &.is-collapsed {
+ width: initial;
+ }
+
+ &:empty {
+ width: initial;
+ }
+
+ .is-fullscreen-mode & {
+ // leave room for block inserter, undo and redo, list view
+ // and some margin left
+ margin-left: $grid-unit-80 * 4 - 2 * $grid-unit;
+
+ top: 0;
+
+ &.is-collapsed {
+ width: initial;
+ }
+
+ &:empty {
+ width: initial;
+ }
+ }
+
+ .show-icon-labels & {
+ width: calc(100% + 40px - #{$toolbar-margin}); //there are no undo, redo and list view buttons
+ margin-left: $grid-unit-80 + 2 * $grid-unit; // inserter and margin
+
+ .is-fullscreen-mode & {
+ margin-left: $grid-unit * 18; // site hub, inserter and margin
+ }
+ }
+ }
+
+ // on desktop viewports the toolbar is fixed
+ // on top of interface header and leaves room
+ // for the block inserter the publish button
+ @include break-large() {
+ width: auto;
+ .show-icon-labels & {
+ width: auto; //there are no undo, redo and list view buttons
+ }
+
+ .is-fullscreen-mode & {
+ // in full screen mode we need to account for
+ // the combined with of the tools at the right of the header and the margin left
+ // of the toolbar which includes four buttons
+ width: calc(100% - 280px - #{4 * $grid-unit-80});
+ }
+ }
+}
diff --git a/packages/edit-site/src/components/block-editor/style.scss b/packages/edit-site/src/components/block-editor/style.scss
index 15fc8e180f818f..0ed00c91097a30 100644
--- a/packages/edit-site/src/components/block-editor/style.scss
+++ b/packages/edit-site/src/components/block-editor/style.scss
@@ -173,3 +173,89 @@
}
}
+// Fixed contextual toolbar
+@include editor-left(".edit-site-visual-editor .block-editor-block-contextual-toolbar.is-fixed");
+
+.edit-site-visual-editor .block-editor-block-contextual-toolbar.is-fixed {
+ position: sticky;
+ top: 0;
+ z-index: z-index(".block-editor-block-popover");
+ display: block;
+ width: 100%;
+
+ // on desktop and tablet viewports the toolbar is fixed
+ // on top of interface header
+ $toolbar-margin: $grid-unit-80 * 3 - 2 * $grid-unit + $grid-unit-05;
+
+ @include break-medium() {
+ // leave room for block inserter, undo and redo, list view
+ margin-left: $toolbar-margin;
+ // position on top of interface header
+ position: fixed;
+ top: $admin-bar-height;
+ // Don't fill up when empty
+ min-height: initial;
+ // has to be flex for collapse button to fit
+ display: flex;
+
+ // Mimic the height of the parent, vertically align center, and provide a max-height.
+ height: $header-height;
+ align-items: center;
+
+
+ // on tablet viewports the toolbar is fixed
+ // on top of interface header and covers the whole header
+ // except for the inserter on the left
+ width: calc(100% - #{$toolbar-margin});
+
+ &.is-collapsed {
+ width: initial;
+ }
+
+ &:empty {
+ width: initial;
+ }
+
+ .is-fullscreen-mode & {
+ // leave room for block inserter, undo and redo, list view
+ // and some margin left
+ margin-left: $grid-unit-80 * 4 - 2 * $grid-unit;
+
+ top: 0;
+
+ &.is-collapsed {
+ width: initial;
+ }
+
+ &:empty {
+ width: initial;
+ }
+ }
+
+ .show-icon-labels & {
+ margin-left: $grid-unit-80 + 2 * $grid-unit; // inserter and margin
+ width: calc(100% + 40px - #{$toolbar-margin}); //there are no undo, redo and list view buttons
+
+ .is-fullscreen-mode & {
+ margin-left: $grid-unit * 18; // site hub, inserter and margin
+ }
+ }
+ }
+
+ // on desktop viewports the toolbar is fixed
+ // on top of interface header and leaves room
+ // for the block inserter the publish button
+ @include break-large() {
+ width: auto;
+ .show-icon-labels & {
+ width: auto; //there are no undo, redo and list view buttons
+ }
+
+ .is-fullscreen-mode & {
+ // in full screen mode we need to account for
+ // the combined with of the tools at the right of the header and the margin left
+ // of the toolbar which includes four buttons
+ width: calc(100% - 280px - #{4 * $grid-unit-80});
+ }
+ }
+}
diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss b/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss
index 97a084b220393d..35e83c7339e1db 100644
--- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss
+++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss
@@ -35,3 +35,101 @@
}
}
}
+
+// Fixed contextual toolbar
+@include editor-left(".edit-widgets-block-editor .block-editor-block-contextual-toolbar.is-fixed");
+
+
+.edit-widgets-block-editor .block-editor-block-contextual-toolbar.is-fixed {
+ position: sticky;
+ top: 0;
+ z-index: z-index(".block-editor-block-popover");
+ display: block;
+ width: 100%;
+
+ // on desktop and tablet viewports the toolbar is fixed
+ // on top of interface header
+ $toolbar-margin: $grid-unit-80 * 3 - 2 * $grid-unit + $grid-unit-05;
+
+ @include break-medium() {
+ // leave room for block inserter, undo and redo, list view
+ margin-left: $toolbar-margin;
+ // position on top of interface header
+ position: fixed;
+ top: $admin-bar-height;
+ // Don't fill up when empty
+ min-height: initial;
+ // remove the border
+ border-bottom: none;
+ // has to be flex for collapse button to fit
+ display: flex;
+
+ // Mimic the height of the parent, vertically align center, and provide a max-height.
+ height: $header-height;
+ align-items: center;
+
+
+ // on tablet viewports the toolbar is fixed
+ // on top of interface header and covers the whole header
+ // except for the inserter on the left
+ width: calc(100% - #{$toolbar-margin});
+
+ &.is-collapsed {
+ width: initial;
+ }
+
+ &:empty {
+ width: initial;
+ }
+
+ .is-fullscreen-mode & {
+ // leave room for block inserter, undo and redo, list view
+ // and some margin left
+ margin-left: $grid-unit-80 * 4 - 2 * $grid-unit;
+
+ top: 0;
+
+ &.is-collapsed {
+ width: initial;
+ }
+
+ &:empty {
+ width: initial;
+ }
+ }
+
+ .show-icon-labels & {
+ margin-left: $grid-unit-80 + 2 * $grid-unit; // inserter and margin
+ width: calc(100% + 40px - #{$toolbar-margin}); //there are no undo, redo and list view buttons
+
+ .is-fullscreen-mode & {
+ margin-left: $grid-unit * 18; // site hub, inserter and margin
+ }
+ }
+
+ .blocks-widgets-container & {
+ margin-left: $grid-unit-80 * 2.4;
+
+ &.is-collapsed {
+ margin-left: $grid-unit-80 * 4.2;
+ }
+ }
+ }
+
+ // on desktop viewports the toolbar is fixed
+ // on top of interface header and leaves room
+ // for the block inserter the publish button
+ @include break-large() {
+ width: auto;
+ .show-icon-labels & {
+ width: auto; //there are no undo, redo and list view buttons
+ }
+
+ .is-fullscreen-mode & {
+ // in full screen mode we need to account for
+ // the combined with of the tools at the right of the header and the margin left
+ // of the toolbar which includes four buttons
+ width: calc(100% - 280px - #{4 * $grid-unit-80});
+ }
+ }
+}
diff --git a/storybook/stories/playground/index.story.js b/storybook/stories/playground/index.story.js
index bde670adc009d4..d3d44e0dedf707 100644
--- a/storybook/stories/playground/index.story.js
+++ b/storybook/stories/playground/index.story.js
@@ -33,7 +33,11 @@ function App() {
} );
return (
-
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+
event.stopPropagation() }
+ >
{
return ;
};
+
+function EditorBox() {
+ const [ blocks, updateBlocks ] = useState( [] );
+
+ useEffect( () => {
+ registerCoreBlocks();
+ }, [] );
+
+ return (
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+ event.stopPropagation() }
+ >
+
+
+
+
+
+ );
+}
+
+export const Box = () => {
+ return ;
+};
From 397b2bf4fdc57866383deb31d69dd05778c4b505 Mon Sep 17 00:00:00 2001
From: Jarda Snajdr
Date: Tue, 12 Sep 2023 12:30:13 +0200
Subject: [PATCH 25/42] Popover: remove custom frame scroll/resize listeners
(#54286)
* Popover: remove custom frame scroll/resize listeners
* Upgrade floating-ui/utils to v0.1.2
* Add changelog entry
---
package-lock.json | 12 ++--
packages/components/CHANGELOG.md | 1 +
packages/components/src/popover/index.tsx | 43 ------------
packages/components/src/popover/utils.ts | 85 +++++------------------
4 files changed, 24 insertions(+), 117 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 530c3d48d08708..c2843123b40dcc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3814,9 +3814,9 @@
}
},
"node_modules/@floating-ui/utils": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz",
- "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw=="
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz",
+ "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ=="
},
"node_modules/@hapi/hoek": {
"version": "9.2.1",
@@ -59101,9 +59101,9 @@
}
},
"@floating-ui/utils": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz",
- "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw=="
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz",
+ "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ=="
},
"@hapi/hoek": {
"version": "9.2.1",
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index d0e758045f4817..adabc2b4412dee 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -25,6 +25,7 @@
- `Popover`: Remove unused `overlay` type from `positionToPlacement` utility function ([#54101](https://github.com/WordPress/gutenberg/pull/54101)).
- `Higher Order` -- `with-focus-outside`: Convert to TypeScript ([#53980](https://github.com/WordPress/gutenberg/pull/53980)).
- `IsolatedEventContainer`: Convert unit test to TypeScript ([#54316](https://github.com/WordPress/gutenberg/pull/54316)).
+- `Popover`: Remove `scroll` and `resize` listeners for iframe overflow parents and rely on recently added native Floating UI support ([#54286](https://github.com/WordPress/gutenberg/pull/54286)).
### Experimental
diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx
index 403f10de57a425..bb51252b549c52 100644
--- a/packages/components/src/popover/index.tsx
+++ b/packages/components/src/popover/index.tsx
@@ -40,7 +40,6 @@ import {
import { close } from '@wordpress/icons';
import deprecated from '@wordpress/deprecated';
import { Path, SVG } from '@wordpress/primitives';
-import { getScrollContainer } from '@wordpress/dom';
/**
* Internal dependencies
@@ -52,7 +51,6 @@ import {
computePopoverPosition,
positionToPlacement,
placementToMotionAnimationProps,
- getReferenceOwnerDocument,
getReferenceElement,
} from './utils';
import type { WordPressComponentProps } from '../ui/context';
@@ -199,9 +197,6 @@ const UnforwardedPopover = (
const [ fallbackReferenceElement, setFallbackReferenceElement ] =
useState< HTMLSpanElement | null >( null );
- const [ referenceOwnerDocument, setReferenceOwnerDocument ] = useState<
- Document | undefined
- >();
const anchorRefFallback: RefCallback< HTMLSpanElement > = useCallback(
( node ) => {
@@ -315,15 +310,6 @@ const UnforwardedPopover = (
?.current;
useLayoutEffect( () => {
- const resultingReferenceOwnerDoc = getReferenceOwnerDocument( {
- anchor,
- anchorRef,
- anchorRect,
- getAnchorRect,
- fallbackReferenceElement,
- fallbackDocument: document,
- } );
-
const resultingReferenceElement = getReferenceElement( {
anchor,
anchorRef,
@@ -333,8 +319,6 @@ const UnforwardedPopover = (
} );
refs.setReference( resultingReferenceElement );
-
- setReferenceOwnerDocument( resultingReferenceOwnerDoc );
}, [
anchor,
anchorRef,
@@ -348,33 +332,6 @@ const UnforwardedPopover = (
refs,
] );
- // If the reference element is in a different ownerDocument (e.g. iFrame),
- // we need to manually update the floating's position as the reference's owner
- // document scrolls.
- useLayoutEffect( () => {
- if (
- ! referenceOwnerDocument ||
- ! referenceOwnerDocument.defaultView
- ) {
- return;
- }
-
- const { defaultView } = referenceOwnerDocument;
- const { frameElement } = defaultView;
-
- const scrollContainer = frameElement
- ? getScrollContainer( frameElement )
- : null;
-
- defaultView.addEventListener( 'resize', update );
- scrollContainer?.addEventListener( 'scroll', update );
-
- return () => {
- defaultView.removeEventListener( 'resize', update );
- scrollContainer?.removeEventListener( 'scroll', update );
- };
- }, [ referenceOwnerDocument, update ] );
-
const mergedFloatingRef = useMergeRefs( [
refs.setFloating,
dialogRef,
diff --git a/packages/components/src/popover/utils.ts b/packages/components/src/popover/utils.ts
index e03ef036803309..50e452222677e9 100644
--- a/packages/components/src/popover/utils.ts
+++ b/packages/components/src/popover/utils.ts
@@ -3,11 +3,7 @@
*/
// eslint-disable-next-line no-restricted-imports
import type { MotionProps } from 'framer-motion';
-import type {
- Placement,
- ReferenceType,
- VirtualElement,
-} from '@floating-ui/react-dom';
+import type { Placement, ReferenceType } from '@floating-ui/react-dom';
/**
* Internal dependencies
@@ -142,58 +138,17 @@ export const placementToMotionAnimationProps = (
};
};
-export const getReferenceOwnerDocument = ( {
- anchor,
- anchorRef,
- anchorRect,
- getAnchorRect,
- fallbackReferenceElement,
- fallbackDocument,
-}: Pick<
- PopoverProps,
- 'anchorRef' | 'anchorRect' | 'getAnchorRect' | 'anchor'
-> & {
- fallbackReferenceElement: Element | null;
- fallbackDocument: Document;
-} ): Document => {
- // In floating-ui's terms:
- // - "reference" refers to the popover's anchor element.
- // - "floating" refers the floating popover's element.
- // A floating element can also be positioned relative to a virtual element,
- // instead of a real one. A virtual element is represented by an object
- // with the `getBoundingClientRect()` function (like real elements).
- // See https://floating-ui.com/docs/virtual-elements for more info.
- let resultingReferenceOwnerDoc;
- if ( ( anchor as VirtualElement )?.contextElement ) {
- resultingReferenceOwnerDoc = ( anchor as VirtualElement ).contextElement
- ?.ownerDocument;
- } else if ( anchor ) {
- resultingReferenceOwnerDoc = anchor.ownerDocument;
- } else if ( ( anchorRef as PopoverAnchorRefTopBottom | undefined )?.top ) {
- resultingReferenceOwnerDoc = ( anchorRef as PopoverAnchorRefTopBottom )
- ?.top.ownerDocument;
- } else if ( ( anchorRef as Range | undefined )?.startContainer ) {
- resultingReferenceOwnerDoc = ( anchorRef as Range ).startContainer
- .ownerDocument;
- } else if (
- ( anchorRef as PopoverAnchorRefReference | undefined )?.current
- ) {
- resultingReferenceOwnerDoc = (
- ( anchorRef as PopoverAnchorRefReference ).current as Element
- ).ownerDocument;
- } else if ( anchorRef as Element | undefined ) {
- // This one should be deprecated.
- resultingReferenceOwnerDoc = ( anchorRef as Element ).ownerDocument;
- } else if ( anchorRect && anchorRect?.ownerDocument ) {
- resultingReferenceOwnerDoc = anchorRect.ownerDocument;
- } else if ( getAnchorRect ) {
- resultingReferenceOwnerDoc = getAnchorRect(
- fallbackReferenceElement
- )?.ownerDocument;
- }
+function isTopBottom(
+ anchorRef: PopoverProps[ 'anchorRef' ]
+): anchorRef is PopoverAnchorRefTopBottom {
+ return !! ( anchorRef as PopoverAnchorRefTopBottom )?.top;
+}
- return resultingReferenceOwnerDoc ?? fallbackDocument;
-};
+function isRef(
+ anchorRef: PopoverProps[ 'anchorRef' ]
+): anchorRef is PopoverAnchorRefReference {
+ return !! ( anchorRef as PopoverAnchorRefReference )?.current;
+}
export const getReferenceElement = ( {
anchor,
@@ -211,19 +166,15 @@ export const getReferenceElement = ( {
if ( anchor ) {
referenceElement = anchor;
- } else if ( ( anchorRef as PopoverAnchorRefTopBottom | undefined )?.top ) {
+ } else if ( isTopBottom( anchorRef ) ) {
// Create a virtual element for the ref. The expectation is that
// if anchorRef.top is defined, then anchorRef.bottom is defined too.
// Seems to be used by the block toolbar, when multiple blocks are selected
// (top and bottom blocks are used to calculate the resulting rect).
referenceElement = {
getBoundingClientRect() {
- const topRect = (
- anchorRef as PopoverAnchorRefTopBottom
- ).top.getBoundingClientRect();
- const bottomRect = (
- anchorRef as PopoverAnchorRefTopBottom
- ).bottom.getBoundingClientRect();
+ const topRect = anchorRef.top.getBoundingClientRect();
+ const bottomRect = anchorRef.bottom.getBoundingClientRect();
return new window.DOMRect(
topRect.x,
topRect.y,
@@ -232,12 +183,10 @@ export const getReferenceElement = ( {
);
},
};
- } else if (
- ( anchorRef as PopoverAnchorRefReference | undefined )?.current
- ) {
+ } else if ( isRef( anchorRef ) ) {
// Standard React ref.
- referenceElement = ( anchorRef as PopoverAnchorRefReference ).current;
- } else if ( anchorRef as Element | undefined ) {
+ referenceElement = anchorRef.current;
+ } else if ( anchorRef ) {
// If `anchorRef` holds directly the element's value (no `current` key)
// This is a weird scenario and should be deprecated.
referenceElement = anchorRef as Element;
From fb0a6aff91059e1e4e0d7dca46b54a419b51c1fa Mon Sep 17 00:00:00 2001
From: Ben Dwyer
Date: Tue, 12 Sep 2023 11:47:39 +0100
Subject: [PATCH 26/42] Theme Previews: Make the back button customizable
(#54242)
* Theme Previews: Make the back button customizable
* Add a comment about a filter
* Theme Previews: Make the backlink text customizable
* Fix PHPCS errors
---------
Co-authored-by: okmttdhr
---
lib/compat/wordpress-6.4/theme-previews.php | 27 +++++++++++++++++++
lib/load.php | 1 +
.../sidebar-navigation-screen/index.js | 13 +++------
3 files changed, 32 insertions(+), 9 deletions(-)
create mode 100644 lib/compat/wordpress-6.4/theme-previews.php
diff --git a/lib/compat/wordpress-6.4/theme-previews.php b/lib/compat/wordpress-6.4/theme-previews.php
new file mode 100644
index 00000000000000..5755fc992921d4
--- /dev/null
+++ b/lib/compat/wordpress-6.4/theme-previews.php
@@ -0,0 +1,27 @@
+ {
+ const { dashboardLink, dashboardLinkText } = useSelect( ( select ) => {
const { getSettings } = unlock( select( editSiteStore ) );
return {
dashboardLink: getSettings().__experimentalDashboardLink,
+ dashboardLinkText: getSettings().__experimentalDashboardLinkText,
};
}, [] );
const { getTheme } = useSelect( coreStore );
@@ -92,15 +93,9 @@ export default function SidebarNavigationScreen( {
) }
Date: Tue, 12 Sep 2023 03:59:12 -0700
Subject: [PATCH 27/42] Add children back to toolbar item render for rendered
components (#53314)
* Add children back to toolbar item render for rendered components
* Add changelog entry
* Address feedback
* More CHANGELOG entry to unreleased section
* format
---------
Co-authored-by: Marco Ciampini
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/toolbar/toolbar-item/index.tsx | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index adabc2b4412dee..2e88ccc3e23076 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -14,6 +14,7 @@
- `Tooltip`: dynamically render in the DOM only when visible ([#54312](https://github.com/WordPress/gutenberg/pull/54312)).
- `PaletteEdit`: Fix padding in RTL languages ([#54034](https://github.com/WordPress/gutenberg/pull/54034)).
+- `ToolbarItem`: Fix children not showing in rendered components ([#53314](https://github.com/WordPress/gutenberg/pull/53314)).
- `CircularOptionPicker`: make focus styles resilient to button size changes ([#54196](https://github.com/WordPress/gutenberg/pull/54196)).
### Internal
diff --git a/packages/components/src/toolbar/toolbar-item/index.tsx b/packages/components/src/toolbar/toolbar-item/index.tsx
index ccfe5084545577..3bf55aedd8a48a 100644
--- a/packages/components/src/toolbar/toolbar-item/index.tsx
+++ b/packages/components/src/toolbar/toolbar-item/index.tsx
@@ -43,7 +43,9 @@ function ToolbarItem(
return children( allProps );
}
- const render = isRenderProp ? children : Component && ;
+ const render = isRenderProp
+ ? children
+ : Component && { children } ;
return (
Date: Tue, 12 Sep 2023 22:13:01 +0900
Subject: [PATCH 28/42] Edit Widgets: Fix broken layout (#54372)
* Edit Widgets: Fix broken layout
* Add copy handler
---
.../index.js | 23 +++++++++----------
.../style.scss | 3 +--
.../index.js | 7 ++++--
3 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js
index 22a5b352ea580e..12a70e2e4da279 100644
--- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js
+++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js
@@ -4,7 +4,9 @@
import {
BlockList,
BlockTools,
- privateApis as blockEditorPrivateApis,
+ BlockSelectionClearer,
+ WritingFlow,
+ __unstableEditorStyles as EditorStyles,
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { useMemo } from '@wordpress/element';
@@ -15,11 +17,6 @@ import { store as preferencesStore } from '@wordpress/preferences';
*/
import Notices from '../notices';
import KeyboardShortcuts from '../keyboard-shortcuts';
-import { unlock } from '../../lock-unlock';
-
-const { ExperimentalBlockCanvas: BlockCanvas } = unlock(
- blockEditorPrivateApis
-);
export default function WidgetAreasBlockEditorContent( {
blockEditorSettings,
@@ -42,13 +39,15 @@ export default function WidgetAreasBlockEditorContent( {
-
-
-
+ scope=".editor-styles-wrapper"
+ />
+
+
+
+
+
);
diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss b/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss
index 35e83c7339e1db..062214ef147bf1 100644
--- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss
+++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/style.scss
@@ -11,8 +11,7 @@
flex-grow: 1;
> div:last-of-type,
- .block-editor-writing-flow,
- .block-editor-writing-flow > div {
+ .block-editor-writing-flow {
display: flex;
flex-direction: column;
flex-grow: 1;
diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js
index d538e37ad1ec8a..f3a7b84d5ded68 100644
--- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js
+++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js
@@ -10,7 +10,10 @@ import {
useResourcePermissions,
} from '@wordpress/core-data';
import { useMemo } from '@wordpress/element';
-import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
+import {
+ CopyHandler,
+ privateApis as blockEditorPrivateApis,
+} from '@wordpress/block-editor';
import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns';
import { store as preferencesStore } from '@wordpress/preferences';
@@ -104,7 +107,7 @@ export default function WidgetAreasBlockEditorProvider( {
useSubRegistry={ false }
{ ...props }
>
- { children }
+
{ children }
From 699819ccede2a14321ccb17137662de60accb687 Mon Sep 17 00:00:00 2001
From: Jonny Harris
Date: Tue, 12 Sep 2023 15:16:55 +0100
Subject: [PATCH 29/42] Deprecated `get_file_path_from_theme` method. (#45831)
* Deprecated function
* Update lib/class-wp-theme-json-resolver-gutenberg.php
Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com>
---------
Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com>
---
lib/class-wp-theme-json-resolver-gutenberg.php | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php
index 39721742946cd1..c5a4c662221aeb 100644
--- a/lib/class-wp-theme-json-resolver-gutenberg.php
+++ b/lib/class-wp-theme-json-resolver-gutenberg.php
@@ -241,9 +241,9 @@ public static function get_theme_data( $deprecated = array(), $options = array()
$options = wp_parse_args( $options, array( 'with_supports' => true ) );
if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) {
- $theme_json_file = static::get_file_path_from_theme( 'theme.json' );
$wp_theme = wp_get_theme();
- if ( '' !== $theme_json_file ) {
+ $theme_json_file = $wp_theme->get_file_path( 'theme.json' );
+ if ( is_readable( $theme_json_file ) ) {
$theme_json_data = static::read_json_file( $theme_json_file );
$theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) );
} else {
@@ -263,8 +263,8 @@ public static function get_theme_data( $deprecated = array(), $options = array()
if ( $wp_theme->parent() ) {
// Get parent theme.json.
- $parent_theme_json_file = static::get_file_path_from_theme( 'theme.json', true );
- if ( '' !== $parent_theme_json_file ) {
+ $parent_theme_json_file = $wp_theme->parent()->get_file_path( 'theme.json' );
+ if ( $theme_json_file !== $parent_theme_json_file && is_readable( $parent_theme_json_file ) ) {
$parent_theme_json_data = static::read_json_file( $parent_theme_json_file );
$parent_theme_json_data = static::translate( $parent_theme_json_data, $wp_theme->parent()->get( 'TextDomain' ) );
$parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data );
@@ -670,6 +670,8 @@ public static function theme_has_support() {
* @return string The whole file path or empty if the file doesn't exist.
*/
protected static function get_file_path_from_theme( $file_name, $template = false ) {
+ // TODO: Remove this method from core on 6.3 release.
+ _deprecated_function( __METHOD__, '6.3.0' );
$path = $template ? get_template_directory() : get_stylesheet_directory();
$candidate = $path . '/' . $file_name;
From 7d054a2d08d86291041fb2be35c3bbf694c18af1 Mon Sep 17 00:00:00 2001
From: Andrew Serong <14988353+andrewserong@users.noreply.github.com>
Date: Wed, 13 Sep 2023 00:20:31 +1000
Subject: [PATCH 30/42] Background Image block support: Add reset menu item
(#54341)
---
packages/block-editor/src/hooks/background.js | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js
index 2b8f355c344989..25815ad5d2cae1 100644
--- a/packages/block-editor/src/hooks/background.js
+++ b/packages/block-editor/src/hooks/background.js
@@ -8,6 +8,7 @@ import {
Button,
DropZone,
FlexItem,
+ MenuItem,
__experimentalItemGroup as ItemGroup,
__experimentalHStack as HStack,
__experimentalTruncate as Truncate,
@@ -236,7 +237,13 @@ function BackgroundImagePanelItem( props ) {
/>
}
variant="secondary"
- />
+ >
+ resetBackgroundImage( props ) }
+ >
+ { __( 'Reset ' ) }
+
+
) }
{ ! url && (
From bba4bbf8c998432159c6b1ea7db40bfe4fc23d5d Mon Sep 17 00:00:00 2001
From: Nik Tsekouras
Date: Tue, 12 Sep 2023 17:36:39 +0300
Subject: [PATCH 31/42] [Site Editor - Page Inspector]: Add ability to switch
templates (#51477)
* [Site Editor - Page Inspector]: Add ability to switch templates
* try setting the page/template from edited entity record
* Update label
* Truncate
* update z-index of templates swap modal
* fix typo
* add + update tests
* fix suggestions per template's `postTypes` and test
* change order of calling in `onTemplateSelect`
---------
Co-authored-by: James Koster
---
.../data/data-core-edit-site.md | 2 +-
packages/base-styles/_z-index.scss | 1 +
.../core-data/src/hooks/use-entity-record.ts | 4 +-
.../page-panels/edit-template.js | 80 ++++++-----
.../sidebar-edit-mode/page-panels/hooks.js | 83 ++++++++++++
.../sidebar-edit-mode/page-panels/index.js | 4 -
.../page-panels/page-summary.js | 2 +
.../page-panels/reset-default-template.js | 44 ++++++
.../sidebar-edit-mode/page-panels/style.scss | 51 +++++--
.../page-panels/swap-template-button.js | 82 +++++++++++
packages/edit-site/src/store/actions.js | 52 +++++--
test/e2e/specs/site-editor/pages.spec.js | 128 +++++++++++++++---
.../emptytheme/templates/custom-template.html | 3 +
test/emptytheme/theme.json | 7 +
14 files changed, 460 insertions(+), 83 deletions(-)
create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js
create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js
create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js
create mode 100644 test/emptytheme/templates/custom-template.html
diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md
index 0cac2268b2ab29..6dea8e9b77d1b2 100644
--- a/docs/reference-guides/data/data-core-edit-site.md
+++ b/docs/reference-guides/data/data-core-edit-site.md
@@ -291,7 +291,7 @@ _Parameters_
_Returns_
-- `number`: The resolved template ID for the page route.
+- `Object`: Action object.
### setHasPageContentFocus
diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss
index 4f8bbab5a16095..12443a30a96656 100644
--- a/packages/base-styles/_z-index.scss
+++ b/packages/base-styles/_z-index.scss
@@ -127,6 +127,7 @@ $z-layers: (
".block-editor-template-part__selection-modal": 1000001,
".block-editor-block-rename-modal": 1000001,
".edit-site-list__rename-modal": 1000001,
+ ".edit-site-swap-template-modal": 1000001,
// Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts
// because it uses emotion and not sass. We need it to render on top its parent popover.
diff --git a/packages/core-data/src/hooks/use-entity-record.ts b/packages/core-data/src/hooks/use-entity-record.ts
index 01eee562644cf1..7f3630b1dd4ceb 100644
--- a/packages/core-data/src/hooks/use-entity-record.ts
+++ b/packages/core-data/src/hooks/use-entity-record.ts
@@ -155,8 +155,8 @@ export default function useEntityRecord< RecordType >(
const mutations = useMemo(
() => ( {
- edit: ( record ) =>
- editEntityRecord( kind, name, recordId, record ),
+ edit: ( record, editOptions: any = {} ) =>
+ editEntityRecord( kind, name, recordId, record, editOptions ),
save: ( saveOptions: any = {} ) =>
saveEditedEntityRecord( kind, name, recordId, {
throwOnError: true,
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js
index b49e8ac459e3fe..2295ee12f45049 100644
--- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/edit-template.js
@@ -2,21 +2,31 @@
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
-import { useMemo } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
-import { BlockContextProvider, BlockPreview } from '@wordpress/block-editor';
-import { Button, __experimentalVStack as VStack } from '@wordpress/components';
+import {
+ DropdownMenu,
+ MenuGroup,
+ MenuItem,
+ __experimentalHStack as HStack,
+ __experimentalText as Text,
+} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { store as coreStore } from '@wordpress/core-data';
-import { parse } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { store as editSiteStore } from '../../../store';
+import SwapTemplateButton from './swap-template-button';
+import ResetDefaultTemplate from './reset-default-template';
+
+const POPOVER_PROPS = {
+ className: 'edit-site-page-panels-edit-template__dropdown',
+ placement: 'bottom-start',
+};
export default function EditTemplate() {
- const { context, hasResolved, template } = useSelect( ( select ) => {
+ const { hasResolved, template } = useSelect( ( select ) => {
const { getEditedPostContext, getEditedPostType, getEditedPostId } =
select( editSiteStore );
const { getEditedEntityRecord, hasFinishedResolution } =
@@ -39,39 +49,43 @@ export default function EditTemplate() {
const { setHasPageContentFocus } = useDispatch( editSiteStore );
- const blockContext = useMemo(
- () => ( { ...context, postType: null, postId: null } ),
- [ context ]
- );
-
- const blocks = useMemo(
- () =>
- template.blocks ??
- ( template.content && typeof template.content !== 'function'
- ? parse( template.content )
- : [] ),
- [ template.blocks, template.content ]
- );
-
if ( ! hasResolved ) {
return null;
}
return (
-
- { decodeEntities( template.title ) }
-
-
-
-
-
- setHasPageContentFocus( false ) }
+
+
+ { __( 'Template' ) }
+
+
- { __( 'Edit template' ) }
-
-
+ { ( { onClose } ) => (
+ <>
+
+ {
+ setHasPageContentFocus( false );
+ onClose();
+ } }
+ >
+ { __( 'Edit template' ) }
+
+
+
+
+ >
+ ) }
+
+
);
}
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js
new file mode 100644
index 00000000000000..3000d21ab13662
--- /dev/null
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js
@@ -0,0 +1,83 @@
+/**
+ * WordPress dependencies
+ */
+import { useSelect } from '@wordpress/data';
+import { useMemo } from '@wordpress/element';
+import { store as coreStore } from '@wordpress/core-data';
+
+/**
+ * Internal dependencies
+ */
+import { store as editSiteStore } from '../../../store';
+
+export function useEditedPostContext() {
+ return useSelect(
+ ( select ) => select( editSiteStore ).getEditedPostContext(),
+ []
+ );
+}
+
+export function useIsPostsPage() {
+ const { postId } = useEditedPostContext();
+ return useSelect(
+ ( select ) =>
+ +postId ===
+ select( coreStore ).getEntityRecord( 'root', 'site' )
+ ?.page_for_posts,
+ [ postId ]
+ );
+}
+
+function useTemplates() {
+ return useSelect(
+ ( select ) =>
+ select( coreStore ).getEntityRecords( 'postType', 'wp_template', {
+ per_page: -1,
+ post_type: 'page',
+ } ),
+ []
+ );
+}
+
+export function useAvailableTemplates() {
+ const currentTemplateSlug = useCurrentTemplateSlug();
+ const isPostsPage = useIsPostsPage();
+ const templates = useTemplates();
+ return useMemo(
+ () =>
+ // The posts page template cannot be changed.
+ ! isPostsPage &&
+ templates?.filter(
+ ( template ) =>
+ template.is_custom &&
+ template.slug !== currentTemplateSlug &&
+ !! template.content.raw // Skip empty templates.
+ ),
+ [ templates, currentTemplateSlug, isPostsPage ]
+ );
+}
+
+export function useCurrentTemplateSlug() {
+ const { postType, postId } = useEditedPostContext();
+ const templates = useTemplates();
+ const entityTemplate = useSelect(
+ ( select ) => {
+ const post = select( coreStore ).getEditedEntityRecord(
+ 'postType',
+ postType,
+ postId
+ );
+ return post?.template;
+ },
+ [ postType, postId ]
+ );
+
+ if ( ! entityTemplate ) {
+ return;
+ }
+ // If a page has a `template` set and is not included in the list
+ // of the theme's templates, do not return it, in order to resolve
+ // to the current theme's default template.
+ return templates?.find( ( template ) => template.slug === entityTemplate )
+ ?.slug;
+}
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js
index 69971d1ad413ae..df59dffe66be69 100644
--- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js
@@ -20,7 +20,6 @@ import { store as editSiteStore } from '../../../store';
import SidebarCard from '../sidebar-card';
import PageContent from './page-content';
import PageSummary from './page-summary';
-import EditTemplate from './edit-template';
export default function PagePanels() {
const { id, type, hasResolved, status, date, password, title, modified } =
@@ -81,9 +80,6 @@ export default function PagePanels() {
-
-
-
>
);
}
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js
index 3dce743b298d45..c4dafeab6cb372 100644
--- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js
@@ -7,6 +7,7 @@ import { __experimentalVStack as VStack } from '@wordpress/components';
*/
import PageStatus from './page-status';
import PublishDate from './publish-date';
+import EditTemplate from './edit-template';
export default function PageSummary( {
status,
@@ -30,6 +31,7 @@ export default function PageSummary( {
postId={ postId }
postType={ postType }
/>
+
);
}
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js
new file mode 100644
index 00000000000000..bc61b82a8d0057
--- /dev/null
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/reset-default-template.js
@@ -0,0 +1,44 @@
+/**
+ * WordPress dependencies
+ */
+import { useDispatch } from '@wordpress/data';
+import { MenuGroup, MenuItem } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { useEntityRecord } from '@wordpress/core-data';
+
+/**
+ * Internal dependencies
+ */
+import {
+ useCurrentTemplateSlug,
+ useEditedPostContext,
+ useIsPostsPage,
+} from './hooks';
+import { store as editSiteStore } from '../../../store';
+
+export default function ResetDefaultTemplate( { onClick } ) {
+ const currentTemplateSlug = useCurrentTemplateSlug();
+ const isPostsPage = useIsPostsPage();
+ const { postType, postId } = useEditedPostContext();
+ const entity = useEntityRecord( 'postType', postType, postId );
+ const { setPage } = useDispatch( editSiteStore );
+ // The default template in a post is indicated by an empty string.
+ if ( ! currentTemplateSlug || isPostsPage ) {
+ return null;
+ }
+ return (
+
+ {
+ entity.edit( { template: '' }, { undoIgnore: true } );
+ onClick();
+ await setPage( {
+ context: { postType, postId },
+ } );
+ } }
+ >
+ { __( 'Reset' ) }
+
+
+ );
+}
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss
index 8c10b32085612b..aedcf5e46ca9ea 100644
--- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss
@@ -1,12 +1,37 @@
-.edit-site-page-panels__edit-template-preview {
- border: 1px solid $gray-200;
- height: 200px;
- max-height: 200px;
- overflow: hidden;
+.edit-site-swap-template-modal {
+ z-index: z-index(".edit-site-swap-template-modal");
}
-.edit-site-page-panels__edit-template-button {
- justify-content: center;
+.edit-site-page-panels__swap-template__confirm-modal__actions {
+ margin-top: $grid-unit-30;
+}
+
+.edit-site-page-panels__swap-template__modal-content .block-editor-block-patterns-list {
+ column-count: 2;
+ column-gap: $grid-unit-30;
+
+ // Small top padding required to avoid cutting off the visible outline when hovering items
+ padding-top: $border-width-focus-fallback;
+
+ @include break-medium() {
+ column-count: 3;
+ }
+
+ @include break-wide() {
+ column-count: 4;
+ }
+
+ .block-editor-block-patterns-list__list-item {
+ break-inside: avoid-column;
+ }
+
+ .block-editor-block-patterns-list__item {
+ // Avoid to override the BlockPatternList component
+ // default hover and focus styles.
+ &:not(:focus):not(:hover) .block-editor-block-preview__container {
+ box-shadow: 0 0 0 1px $gray-300;
+ }
+ }
}
.edit-site-change-status__content {
@@ -36,15 +61,21 @@
.edit-site-summary-field {
.components-dropdown {
- flex-grow: 1;
+ width: 70%;
}
.edit-site-summary-field__trigger {
- width: 100%;
+ max-width: 100%;
+
+ // Truncate
+ display: block;
+ text-align: left;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.edit-site-summary-field__label {
width: 30%;
}
}
-
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js
new file mode 100644
index 00000000000000..fee4f22a3ae2bc
--- /dev/null
+++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/swap-template-button.js
@@ -0,0 +1,82 @@
+/**
+ * WordPress dependencies
+ */
+import { useDispatch } from '@wordpress/data';
+import { useMemo, useState, useCallback } from '@wordpress/element';
+import { decodeEntities } from '@wordpress/html-entities';
+import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor';
+import { MenuItem, Modal } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { useEntityRecord } from '@wordpress/core-data';
+import { parse } from '@wordpress/blocks';
+import { useAsyncList } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import { store as editSiteStore } from '../../../store';
+import { useAvailableTemplates, useEditedPostContext } from './hooks';
+
+export default function SwapTemplateButton( { onClick } ) {
+ const [ showModal, setShowModal ] = useState( false );
+ const availableTemplates = useAvailableTemplates();
+ const onClose = useCallback( () => {
+ setShowModal( false );
+ }, [] );
+ const { postType, postId } = useEditedPostContext();
+ const entitiy = useEntityRecord( 'postType', postType, postId );
+ const { setPage } = useDispatch( editSiteStore );
+ if ( ! availableTemplates?.length ) {
+ return null;
+ }
+ const onTemplateSelect = async ( template ) => {
+ entitiy.edit( { template: template.name }, { undoIgnore: true } );
+ await setPage( {
+ context: { postType, postId },
+ } );
+ onClose(); // Close the template suggestions modal first.
+ onClick();
+ };
+ return (
+ <>
+ setShowModal( true ) }>
+ { __( 'Swap template' ) }
+
+ { showModal && (
+
+
+
+
+
+ ) }
+ >
+ );
+}
+
+function TemplatesList( { onSelect } ) {
+ const availableTemplates = useAvailableTemplates();
+ const templatesAsPatterns = useMemo(
+ () =>
+ availableTemplates.map( ( template ) => ( {
+ name: template.slug,
+ blocks: parse( template.content.raw ),
+ title: decodeEntities( template.title.rendered ),
+ id: template.id,
+ } ) ),
+ [ availableTemplates ]
+ );
+ const shownTemplates = useAsyncList( templatesAsPatterns );
+ return (
+
+ );
+}
diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js
index 0ad521a9c9a54e..f690075c311489 100644
--- a/packages/edit-site/src/store/actions.js
+++ b/packages/edit-site/src/store/actions.js
@@ -4,7 +4,7 @@
import apiFetch from '@wordpress/api-fetch';
import { parse, __unstableSerializeAndClean } from '@wordpress/blocks';
import deprecated from '@wordpress/deprecated';
-import { addQueryArgs, getPathAndQueryString } from '@wordpress/url';
+import { addQueryArgs } from '@wordpress/url';
import { __, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore } from '@wordpress/core-data';
@@ -233,7 +233,7 @@ export function setHomeTemplateId() {
*
* @param {Object} context The context object.
*
- * @return {number} The resolved template ID for the page route.
+ * @return {Object} Action object.
*/
export function setEditedPostContext( context ) {
return {
@@ -257,22 +257,48 @@ export function setEditedPostContext( context ) {
export const setPage =
( page ) =>
async ( { dispatch, registry } ) => {
- if ( ! page.path && page.context?.postId ) {
- const entity = await registry
+ let template;
+ const getDefaultTemplate = async ( slug ) =>
+ apiFetch( {
+ path: addQueryArgs( '/wp/v2/templates/lookup', {
+ slug: `page-${ slug }`,
+ } ),
+ } );
+
+ if ( page.path ) {
+ template = await registry
+ .resolveSelect( coreStore )
+ .__experimentalGetTemplateForLink( page.path );
+ } else {
+ const editedEntity = await registry
.resolveSelect( coreStore )
- .getEntityRecord(
+ .getEditedEntityRecord(
'postType',
- page.context.postType || 'post',
- page.context.postId
+ page.context?.postType || 'post',
+ page.context?.postId
);
- // If the entity is undefined for some reason, path will resolve to "/"
- page.path = getPathAndQueryString( entity?.link );
+ const currentTemplateSlug = editedEntity?.template;
+ if ( currentTemplateSlug ) {
+ const currentTemplate = (
+ await registry
+ .resolveSelect( coreStore )
+ .getEntityRecords( 'postType', 'wp_template', {
+ per_page: -1,
+ } )
+ )?.find( ( { slug } ) => slug === currentTemplateSlug );
+ if ( currentTemplate ) {
+ template = currentTemplate;
+ } else {
+ // If a page has a `template` set and is not included in the list
+ // of the current theme's templates, query for current theme's default template.
+ template = await getDefaultTemplate( editedEntity?.link );
+ }
+ } else {
+ // Page's `template` is empty, that indicates we need to use the default template for the page.
+ template = await getDefaultTemplate( editedEntity?.link );
+ }
}
- const template = await registry
- .resolveSelect( coreStore )
- .__experimentalGetTemplateForLink( page.path );
-
if ( ! template ) {
return;
}
diff --git a/test/e2e/specs/site-editor/pages.spec.js b/test/e2e/specs/site-editor/pages.spec.js
index 1c0cb8c4b69686..e6a0b7f266cbf4 100644
--- a/test/e2e/specs/site-editor/pages.spec.js
+++ b/test/e2e/specs/site-editor/pages.spec.js
@@ -3,33 +3,48 @@
*/
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+async function draftNewPage( page ) {
+ await page.getByRole( 'button', { name: 'Pages' } ).click();
+ await page.getByRole( 'button', { name: 'Draft a new page' } ).click();
+ await page
+ .locator( 'role=dialog[name="Draft a new page"i]' )
+ .locator( 'role=textbox[name="Page title"i]' )
+ .fill( 'Test Page' );
+ await page.keyboard.press( 'Enter' );
+ await expect(
+ page.locator(
+ `role=button[name="Dismiss this notice"i] >> text='"Test Page" successfully created.'`
+ )
+ ).toBeVisible();
+}
+
test.describe( 'Pages', () => {
test.beforeAll( async ( { requestUtils } ) => {
await requestUtils.activateTheme( 'emptytheme' );
+ await Promise.all( [
+ requestUtils.deleteAllTemplates( 'wp_template' ),
+ requestUtils.deleteAllPages(),
+ ] );
} );
test.afterAll( async ( { requestUtils } ) => {
await requestUtils.activateTheme( 'twentytwentyone' );
+ await Promise.all( [
+ requestUtils.deleteAllTemplates( 'wp_template' ),
+ requestUtils.deleteAllPages(),
+ ] );
} );
- test.beforeEach( async ( { admin } ) => {
+ test.beforeEach( async ( { requestUtils, admin } ) => {
+ await Promise.all( [
+ requestUtils.deleteAllTemplates( 'wp_template' ),
+ requestUtils.deleteAllPages(),
+ ] );
await admin.visitSiteEditor();
} );
test( 'create a new page', async ( { page, editor } ) => {
- // Draft a new page.
- await page.getByRole( 'button', { name: 'Pages' } ).click();
- await page.getByRole( 'button', { name: 'Draft a new page' } ).click();
- await page
- .locator( 'role=dialog[name="Draft a new page"i]' )
- .locator( 'role=textbox[name="Page title"i]' )
- .fill( 'Test Page' );
- await page.keyboard.press( 'Enter' );
- await expect(
- page.locator(
- `role=button[name="Dismiss this notice"i] >> text='"Test Page" successfully created.'`
- )
- ).toBeVisible();
+ await draftNewPage( page );
// Insert into Page Content using default block.
await editor.canvas
@@ -79,15 +94,11 @@ test.describe( 'Pages', () => {
// Switch to template editing focus.
await editor.openDocumentSettingsSidebar();
- await expect(
- page.locator(
- '.edit-site-page-panels__edit-template-preview iframe'
- )
- ).toBeVisible();
await page
.getByRole( 'region', { name: 'Editor settings' } )
- .getByRole( 'button', { name: 'Edit template' } )
+ .getByRole( 'button', { name: 'Template options' } )
.click();
+ await page.getByRole( 'button', { name: 'Edit template' } ).click();
await expect(
editor.canvas.getByRole( 'document', {
name: 'Block: Content',
@@ -125,4 +136,81 @@ test.describe( 'Pages', () => {
)
).toBeVisible();
} );
+ test( 'swap template and reset to default', async ( {
+ admin,
+ page,
+ editor,
+ } ) => {
+ // Create a custom template first.
+ const templateName = 'demo';
+ await page.getByRole( 'button', { name: 'Templates' } ).click();
+ await page.getByRole( 'button', { name: 'Add New Template' } ).click();
+ await page
+ .getByRole( 'button', {
+ name: 'A custom template can be manually applied to any post or page.',
+ } )
+ .click();
+ // Fill the template title and submit.
+ const newTemplateDialog = page.locator(
+ 'role=dialog[name="Create custom template"i]'
+ );
+ const templateNameInput = newTemplateDialog.locator(
+ 'role=textbox[name="Name"i]'
+ );
+ await templateNameInput.fill( templateName );
+ await page.keyboard.press( 'Enter' );
+ await page
+ .locator( '.block-editor-block-patterns-list__list-item' )
+ .click();
+ await editor.saveSiteEditorEntities();
+ await admin.visitSiteEditor();
+
+ // Create new page that has the default template so as to swap it.
+ await draftNewPage( page );
+ await editor.openDocumentSettingsSidebar();
+ const templateOptionsButton = page
+ .getByRole( 'region', { name: 'Editor settings' } )
+ .getByRole( 'button', { name: 'Template options' } );
+ await expect( templateOptionsButton ).toHaveText( 'Single Entries' );
+ await templateOptionsButton.click();
+ await page
+ .getByRole( 'menu', { name: 'Template options' } )
+ .getByText( 'Swap template' )
+ .click();
+ const templateItem = page.locator(
+ '.block-editor-block-patterns-list__item-title'
+ );
+ // Empty theme's custom template with `postTypes: ['post']`, should not be suggested.
+ await expect( templateItem ).toHaveCount( 1 );
+ await templateItem.click();
+ await expect( templateOptionsButton ).toHaveText( 'demo' );
+ await editor.saveSiteEditorEntities();
+
+ // Now reset, and apply the default template back.
+ await templateOptionsButton.click();
+ const resetButton = page
+ .getByRole( 'menu', { name: 'Template options' } )
+ .getByText( 'Reset' );
+ await expect( resetButton ).toBeVisible();
+ await resetButton.click();
+ await expect( templateOptionsButton ).toHaveText( 'Single Entries' );
+ } );
+ test( 'swap template options should respect the declared `postTypes`', async ( {
+ page,
+ editor,
+ } ) => {
+ await draftNewPage( page );
+ await editor.openDocumentSettingsSidebar();
+ const templateOptionsButton = page
+ .getByRole( 'region', { name: 'Editor settings' } )
+ .getByRole( 'button', { name: 'Template options' } );
+ await templateOptionsButton.click();
+ // Empty theme has only one custom template with `postTypes: ['post']`,
+ // so it should not be suggested.
+ await expect(
+ page
+ .getByRole( 'menu', { name: 'Template options' } )
+ .getByText( 'Swap template' )
+ ).toHaveCount( 0 );
+ } );
} );
diff --git a/test/emptytheme/templates/custom-template.html b/test/emptytheme/templates/custom-template.html
new file mode 100644
index 00000000000000..e4e8c11a39ef6f
--- /dev/null
+++ b/test/emptytheme/templates/custom-template.html
@@ -0,0 +1,3 @@
+
+Custom template for Posts
+
diff --git a/test/emptytheme/theme.json b/test/emptytheme/theme.json
index b28e6c9f274b2f..d95ed844e6b1cb 100644
--- a/test/emptytheme/theme.json
+++ b/test/emptytheme/theme.json
@@ -8,5 +8,12 @@
"wideSize": "1100px"
}
},
+ "customTemplates": [
+ {
+ "name": "custom-template",
+ "title": "Custom",
+ "postTypes": [ "post" ]
+ }
+ ],
"patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ]
}
From 71f75e734ea6bcdb250220482890c54ae94d2753 Mon Sep 17 00:00:00 2001
From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com>
Date: Tue, 12 Sep 2023 17:07:31 +0200
Subject: [PATCH 32/42] Remove `wp_store` from query block (#54359)
* Remove wp_store from query block
* Add comment
* Remove unnecessary object cast
---------
Co-authored-by: Luis Herranz
---
packages/block-library/src/query/index.php | 26 +++++++++-------------
packages/block-library/src/query/view.js | 8 +++----
2 files changed, 15 insertions(+), 19 deletions(-)
diff --git a/packages/block-library/src/query/index.php b/packages/block-library/src/query/index.php
index f06073cc952000..4b8e8dee02a31a 100644
--- a/packages/block-library/src/query/index.php
+++ b/packages/block-library/src/query/index.php
@@ -23,9 +23,19 @@ function render_block_core_query( $attributes, $content, $block ) {
// Add the necessary directives.
$p->set_attribute( 'data-wp-interactive', true );
$p->set_attribute( 'data-wp-navigation-id', 'query-' . $attributes['queryId'] );
+ // Use context to send translated strings.
$p->set_attribute(
'data-wp-context',
- wp_json_encode( array( 'core' => array( 'query' => (object) array() ) ) )
+ wp_json_encode(
+ array(
+ 'core' => array(
+ 'query' => array(
+ 'loadingText' => __( 'Loading page, please wait.' ),
+ 'loadedText' => __( 'Page Loaded.' ),
+ ),
+ ),
+ )
+ )
);
$content = $p->get_updated_html();
@@ -49,20 +59,6 @@ class="wp-block-query__enhanced-pagination-animation"
$last_div_position,
0
);
-
- // Use state to send translated strings.
- wp_store(
- array(
- 'state' => array(
- 'core' => array(
- 'query' => array(
- 'loadingText' => __( 'Loading page, please wait.' ),
- 'loadedText' => __( 'Page Loaded.' ),
- ),
- ),
- ),
- )
- );
}
}
diff --git a/packages/block-library/src/query/view.js b/packages/block-library/src/query/view.js
index cbd5573e05c6f9..78cc423c80661a 100644
--- a/packages/block-library/src/query/view.js
+++ b/packages/block-library/src/query/view.js
@@ -32,7 +32,7 @@ store( {
actions: {
core: {
query: {
- navigate: async ( { event, ref, context, state } ) => {
+ navigate: async ( { event, ref, context } ) => {
if ( isValidLink( ref ) && isValidEvent( event ) ) {
event.preventDefault();
@@ -42,7 +42,7 @@ store( {
// Don't announce the navigation immediately, wait 300 ms.
const timeout = setTimeout( () => {
context.core.query.message =
- state.core.query.loadingText;
+ context.core.query.loadingText;
context.core.query.animation = 'start';
}, 300 );
@@ -55,9 +55,9 @@ store( {
// same, we use a no-break space similar to the @wordpress/a11y
// package: https://github.com/WordPress/gutenberg/blob/c395242b8e6ee20f8b06c199e4fc2920d7018af1/packages/a11y/src/filter-message.js#L20-L26
context.core.query.message =
- state.core.query.loadedText +
+ context.core.query.loadedText +
( context.core.query.message ===
- state.core.query.loadedText
+ context.core.query.loadedText
? '\u00A0'
: '' );
From d27d9d5f2b80f6c0b935927a7890b17823ec6ca9 Mon Sep 17 00:00:00 2001
From: Rich Tabor
Date: Tue, 12 Sep 2023 12:35:16 -0400
Subject: [PATCH 33/42] Tweak border control button to proper metrics and
simpler action (#53998)
* Fix button styles to updated metrics
* Simplify button text
* Update CHANGELOG.md
* Use proper border top color
* Fix tests
---
packages/components/CHANGELOG.md | 1 +
.../border-control-dropdown/component.tsx | 2 +-
packages/components/src/border-control/styles.ts | 12 ++++++------
packages/components/src/border-control/test/index.js | 4 ++--
4 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 2e88ccc3e23076..5426a4d6d6dda0 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,6 +1,7 @@
## Unreleased
+- `BorderControl`: Apply proper metrics and simpler text ([#53998](https://github.com/WordPress/gutenberg/pull/53998)).
### Enhancements
diff --git a/packages/components/src/border-control/border-control-dropdown/component.tsx b/packages/components/src/border-control/border-control-dropdown/component.tsx
index 64e92ca79e32f4..4a34d6ed7ffe34 100644
--- a/packages/components/src/border-control/border-control-dropdown/component.tsx
+++ b/packages/components/src/border-control/border-control-dropdown/component.tsx
@@ -235,7 +235,7 @@ const BorderControlDropdown = (
onClose();
} }
>
- { __( 'Reset to default' ) }
+ { __( 'Reset' ) }
) }
diff --git a/packages/components/src/border-control/styles.ts b/packages/components/src/border-control/styles.ts
index 7dec1d7dde434d..3aedef1445cc6c 100644
--- a/packages/components/src/border-control/styles.ts
+++ b/packages/components/src/border-control/styles.ts
@@ -165,10 +165,10 @@ export const resetButton = css`
/* Override button component styling */
&& {
- border-top: ${ CONFIG.borderWidth } solid ${ COLORS.gray[ 200 ] };
+ border-top: ${ CONFIG.borderWidth } solid ${ COLORS.gray[ 400 ] };
border-top-left-radius: 0;
border-top-right-radius: 0;
- height: 46px;
+ height: 40px;
}
`;
@@ -180,10 +180,10 @@ export const borderControlStylePicker = css`
export const borderStyleButton = css`
&&&&& {
- min-width: 30px;
- width: 30px;
- height: 30px;
- padding: 3px;
+ min-width: 32px;
+ width: 32px;
+ height: 32px;
+ padding: 4px;
}
`;
diff --git a/packages/components/src/border-control/test/index.js b/packages/components/src/border-control/test/index.js
index 4ca8c1fb8bb67a..c41dce687cc522 100644
--- a/packages/components/src/border-control/test/index.js
+++ b/packages/components/src/border-control/test/index.js
@@ -137,7 +137,7 @@ describe( 'BorderControl', () => {
const solidButton = getButton( 'Solid' );
const dashedButton = getButton( 'Dashed' );
const dottedButton = getButton( 'Dotted' );
- const resetButton = getButton( 'Reset to default' );
+ const resetButton = getButton( 'Reset' );
expect( customColorPicker ).toBeInTheDocument();
expect( colorSwatchButtons.length ).toEqual( colors.length );
@@ -359,7 +359,7 @@ describe( 'BorderControl', () => {
const props = createProps();
render( );
await openPopover( user );
- await user.click( getButton( 'Reset to default' ) );
+ await user.click( getButton( 'Reset' ) );
expect( props.onChange ).toHaveBeenNthCalledWith( 1, {
color: undefined,
From 5331fb6d9f49852fa7e96e5fe1923d49e0a4d611 Mon Sep 17 00:00:00 2001
From: Brooke <35543432+brookewp@users.noreply.github.com>
Date: Tue, 12 Sep 2023 10:29:15 -0700
Subject: [PATCH 34/42] Button: Update test assertion to match test name
(#54260)
* Button: Update test assertion to match test name
* Restore replaced assertion for button
* Update assertions after changes to tooltip in #54312
---
packages/components/src/button/test/index.tsx | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/packages/components/src/button/test/index.tsx b/packages/components/src/button/test/index.tsx
index 7f65337508bd89..d70b8b075aa1eb 100644
--- a/packages/components/src/button/test/index.tsx
+++ b/packages/components/src/button/test/index.tsx
@@ -242,6 +242,8 @@ describe( 'Button', () => {
} );
it( 'should populate tooltip with description content for buttons with visible labels (buttons with children)', async () => {
+ const user = userEvent.setup();
+
render(
{
description: 'Description text',
} )
).toBeVisible();
+
+ await user.tab();
+
+ expect(
+ screen.getByRole( 'tooltip', {
+ name: 'Description text',
+ } )
+ ).toBeVisible();
+
+ await cleanupTooltip( user );
} );
it( 'should allow tooltip disable', async () => {
From 045cb0d4fe786d4fd6ba5505f986271c985a2de1 Mon Sep 17 00:00:00 2001
From: John Hooks
Date: Tue, 12 Sep 2023 11:09:30 -0700
Subject: [PATCH 35/42] refactor(blob): type url parameter as optional (#49201)
---
packages/blob/README.md | 2 +-
packages/blob/src/index.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/blob/README.md b/packages/blob/README.md
index c7957da052e60a..0305c4784d6cbd 100644
--- a/packages/blob/README.md
+++ b/packages/blob/README.md
@@ -56,7 +56,7 @@ Check whether a url is a blob url.
_Parameters_
-- _url_ `string`: The URL.
+- _url_ `string|undefined`: The URL.
_Returns_
diff --git a/packages/blob/src/index.js b/packages/blob/src/index.js
index 67a48e0003150a..496869703d2dac 100644
--- a/packages/blob/src/index.js
+++ b/packages/blob/src/index.js
@@ -60,7 +60,7 @@ export function revokeBlobURL( url ) {
/**
* Check whether a url is a blob url.
*
- * @param {string} url The URL.
+ * @param {string|undefined} url The URL.
*
* @return {boolean} Is the url a blob url?
*/
From af22ab459fc6430df94d452a6fc6951770563756 Mon Sep 17 00:00:00 2001
From: Rich Tabor
Date: Tue, 12 Sep 2023 15:07:28 -0400
Subject: [PATCH 36/42] Use __next40pxDefaultSize on featured image overlay
opacity control (#54389)
---
packages/block-library/src/post-featured-image/overlay.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/block-library/src/post-featured-image/overlay.js b/packages/block-library/src/post-featured-image/overlay.js
index ace5f8a5793b44..33d7047e814921 100644
--- a/packages/block-library/src/post-featured-image/overlay.js
+++ b/packages/block-library/src/post-featured-image/overlay.js
@@ -113,6 +113,7 @@ const Overlay = ( {
max={ 100 }
step={ 10 }
required
+ __next40pxDefaultSize
/>
From 0cf7898b5f251f9bb1b5c2f5f72b8e37016a6949 Mon Sep 17 00:00:00 2001
From: Tonya Mork
Date: Tue, 12 Sep 2023 16:15:38 -0500
Subject: [PATCH 37/42] Ensure font-face styles are printed in iframe editors.
(#54313)
Loads the plugin's `fonts.php` file and moves the hooked code for `'block_editor_settings_all'` out of the guard to ensure the `@font-face` styels are printed in the iframe editors when Gutenberg overrides Core.
---
lib/compat/wordpress-6.4/fonts/fonts.php | 30 ++++++++++++------------
lib/load.php | 8 ++++++-
2 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/lib/compat/wordpress-6.4/fonts/fonts.php b/lib/compat/wordpress-6.4/fonts/fonts.php
index 02ce66c8116462..2e06b6e085d907 100644
--- a/lib/compat/wordpress-6.4/fonts/fonts.php
+++ b/lib/compat/wordpress-6.4/fonts/fonts.php
@@ -34,20 +34,20 @@ function wp_print_font_faces( $fonts = array() ) {
$wp_font_face = new WP_Font_Face();
$wp_font_face->generate_and_print( $fonts );
}
+}
- // @core-merge: do not merge this code into Core.
- add_filter(
- 'block_editor_settings_all',
- static function( $settings ) {
- ob_start();
- // @core-merge: add only this line into Core's `_wp_get_iframed_editor_assets()` function after `wp_print_styles()`.
- wp_print_font_faces();
- $styles = ob_get_clean();
+// @core-merge: do not merge this code into Core.
+add_filter(
+ 'block_editor_settings_all',
+ static function( $settings ) {
+ ob_start();
+ // @core-merge: add only this line into Core's `_wp_get_iframed_editor_assets()` function after `wp_print_styles()`.
+ wp_print_font_faces();
+ $styles = ob_get_clean();
- // Add the font-face styles to iframed editor assets.
- $settings['__unstableResolvedAssets']['styles'] .= $styles;
- return $settings;
- },
- 11
- );
-}
+ // Add the font-face styles to iframed editor assets.
+ $settings['__unstableResolvedAssets']['styles'] .= $styles;
+ return $settings;
+ },
+ 11
+);
diff --git a/lib/load.php b/lib/load.php
index e103231575ad86..95cf413da68533 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -153,9 +153,15 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( ! class_exists( 'WP_Font_Face' ) ) {
require __DIR__ . '/compat/wordpress-6.4/fonts/font-face/class-wp-font-face.php';
require __DIR__ . '/compat/wordpress-6.4/fonts/font-face/class-wp-font-face-resolver.php';
- require __DIR__ . '/compat/wordpress-6.4/fonts/fonts.php';
}
+ /*
+ * As _gutenberg_get_iframed_editor_assets_6_4() overrides Core's _wp_get_iframed_editor_assets(),
+ * load this file to ensure wp_print_font_faces() is invoked to load the styles into the
+ * iframed editor.
+ */
+ require __DIR__ . '/compat/wordpress-6.4/fonts/fonts.php';
+
// Load the BC Layer to avoid fatal errors of extenders using the Fonts API.
// @core-merge: do not merge the BC layer files into WordPress Core.
require __DIR__ . '/experimental/fonts/font-face/bc-layer/class-wp-fonts-provider.php';
From 5179b9c12c83d5121ac61b30ac6db6cba8624ad1 Mon Sep 17 00:00:00 2001
From: Matias Benedetto
Date: Tue, 12 Sep 2023 22:13:43 -0300
Subject: [PATCH 38/42] Indicate nested paths on
__experimentalSaveSpecifiedEntityEdits (#54161)
* be able to indicate nested paths on __experimentalSaveSpecifiedEntityEdits
* fix test decription
* fix test description text
Co-authored-by: Jeff Ong
* adding type checking
Co-authored-by: Jeff Ong
* Format if statement
* Tweak comment wording
* update description
Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com>
* correct function doc
---------
Co-authored-by: Jeff Ong
Co-authored-by: Sarah Norris
Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com>
---
packages/core-data/src/actions.js | 11 ++--
.../core-data/src/utils/get-nested-value.js | 27 ++++++++
packages/core-data/src/utils/index.js | 1 +
.../core-data/src/utils/set-nested-value.js | 18 ++++--
.../src/utils/test/get-nested-value.js | 61 +++++++++++++++++++
.../src/utils/test/set-nested-value.js | 7 +++
6 files changed, 113 insertions(+), 12 deletions(-)
create mode 100644 packages/core-data/src/utils/get-nested-value.js
create mode 100644 packages/core-data/src/utils/test/get-nested-value.js
diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js
index a79d1236682582..3688d643021637 100644
--- a/packages/core-data/src/actions.js
+++ b/packages/core-data/src/actions.js
@@ -14,6 +14,7 @@ import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
+import { getNestedValue, setNestedValue } from './utils';
import { receiveItems, removeItems, receiveQueriedItems } from './queried-data';
import { getOrLoadEntitiesConfig, DEFAULT_ENTITY_KEY } from './entities';
import { createBatch } from './batch';
@@ -792,7 +793,7 @@ export const saveEditedEntityRecord =
* @param {string} kind Kind of the entity.
* @param {string} name Name of the entity.
* @param {Object} recordId ID of the record.
- * @param {Array} itemsToSave List of entity properties to save.
+ * @param {Array} itemsToSave List of entity properties or property paths to save.
* @param {Object} options Saving options.
*/
export const __experimentalSaveSpecifiedEntityEdits =
@@ -807,10 +808,9 @@ export const __experimentalSaveSpecifiedEntityEdits =
recordId
);
const editsToSave = {};
- for ( const edit in edits ) {
- if ( itemsToSave.some( ( item ) => item === edit ) ) {
- editsToSave[ edit ] = edits[ edit ];
- }
+
+ for ( const item of itemsToSave ) {
+ setNestedValue( editsToSave, item, getNestedValue( edits, item ) );
}
const configs = await dispatch( getOrLoadEntitiesConfig( kind ) );
@@ -827,7 +827,6 @@ export const __experimentalSaveSpecifiedEntityEdits =
if ( recordId ) {
editsToSave[ entityIdKey ] = recordId;
}
-
return await dispatch.saveEntityRecord(
kind,
name,
diff --git a/packages/core-data/src/utils/get-nested-value.js b/packages/core-data/src/utils/get-nested-value.js
new file mode 100644
index 00000000000000..0fe210159b07b8
--- /dev/null
+++ b/packages/core-data/src/utils/get-nested-value.js
@@ -0,0 +1,27 @@
+/**
+ * Helper util to return a value from a certain path of the object.
+ * Path is specified as either:
+ * - a string of properties, separated by dots, for example: "x.y".
+ * - an array of properties, for example `[ 'x', 'y' ]`.
+ * You can also specify a default value in case the result is nullish.
+ *
+ * @param {Object} object Input object.
+ * @param {string|Array} path Path to the object property.
+ * @param {*} defaultValue Default value if the value at the specified path is undefined.
+ * @return {*} Value of the object property at the specified path.
+ */
+export default function getNestedValue( object, path, defaultValue ) {
+ if (
+ ! object ||
+ typeof object !== 'object' ||
+ ( typeof path !== 'string' && ! Array.isArray( path ) )
+ ) {
+ return object;
+ }
+ const normalizedPath = Array.isArray( path ) ? path : path.split( '.' );
+ let value = object;
+ normalizedPath.forEach( ( fieldName ) => {
+ value = value?.[ fieldName ];
+ } );
+ return value !== undefined ? value : defaultValue;
+}
diff --git a/packages/core-data/src/utils/index.js b/packages/core-data/src/utils/index.js
index 4f4149c8265b2b..f37efe6eee7fda 100644
--- a/packages/core-data/src/utils/index.js
+++ b/packages/core-data/src/utils/index.js
@@ -7,3 +7,4 @@ export { default as replaceAction } from './replace-action';
export { default as withWeakMapCache } from './with-weak-map-cache';
export { default as isRawAttribute } from './is-raw-attribute';
export { default as setNestedValue } from './set-nested-value';
+export { default as getNestedValue } from './get-nested-value';
diff --git a/packages/core-data/src/utils/set-nested-value.js b/packages/core-data/src/utils/set-nested-value.js
index e90bf23e4dad8e..cb2ae788d1c92c 100644
--- a/packages/core-data/src/utils/set-nested-value.js
+++ b/packages/core-data/src/utils/set-nested-value.js
@@ -4,6 +4,10 @@
* Arrays are created for missing index properties while objects are created
* for all other missing properties.
*
+ * Path is specified as either:
+ * - a string of properties, separated by dots, for example: "x.y".
+ * - an array of properties, for example `[ 'x', 'y' ]`.
+ *
* This function intentionally mutates the input object.
*
* Inspired by _.set().
@@ -12,24 +16,26 @@
*
* @todo Needs to be deduplicated with its copy in `@wordpress/edit-site`.
*
- * @param {Object} object Object to modify
- * @param {Array} path Path of the property to set.
- * @param {*} value Value to set.
+ * @param {Object} object Object to modify
+ * @param {Array|string} path Path of the property to set.
+ * @param {*} value Value to set.
*/
export default function setNestedValue( object, path, value ) {
if ( ! object || typeof object !== 'object' ) {
return object;
}
- path.reduce( ( acc, key, idx ) => {
+ const normalizedPath = Array.isArray( path ) ? path : path.split( '.' );
+
+ normalizedPath.reduce( ( acc, key, idx ) => {
if ( acc[ key ] === undefined ) {
- if ( Number.isInteger( path[ idx + 1 ] ) ) {
+ if ( Number.isInteger( normalizedPath[ idx + 1 ] ) ) {
acc[ key ] = [];
} else {
acc[ key ] = {};
}
}
- if ( idx === path.length - 1 ) {
+ if ( idx === normalizedPath.length - 1 ) {
acc[ key ] = value;
}
return acc[ key ];
diff --git a/packages/core-data/src/utils/test/get-nested-value.js b/packages/core-data/src/utils/test/get-nested-value.js
new file mode 100644
index 00000000000000..1048885d39c674
--- /dev/null
+++ b/packages/core-data/src/utils/test/get-nested-value.js
@@ -0,0 +1,61 @@
+/**
+ * Internal dependencies
+ */
+import getNestedValue from '../get-nested-value';
+
+describe( 'getNestedValue', () => {
+ it( 'should return the same object unmodified if path is an empty array', () => {
+ const input = { x: 'y' };
+ const result = getNestedValue( input, [] );
+ expect( result ).toEqual( input );
+ } );
+
+ it( 'should return the nested value', () => {
+ const input = { x: { y: { z: 123 } } };
+ const result = getNestedValue( input, [ 'x', 'y', 'z' ] );
+
+ expect( result ).toEqual( 123 );
+ } );
+
+ it( 'should return the nested value if the path is a string', () => {
+ const input = { x: { y: { z: 123 } } };
+ const result = getNestedValue( input, 'x.y.z' );
+
+ expect( result ).toEqual( 123 );
+ } );
+
+ it( 'should return the shallow value', () => {
+ const input = { x: { y: { z: 123 } } };
+ const result = getNestedValue( input, 'x' );
+
+ expect( result ).toEqual( { y: { z: 123 } } );
+ } );
+
+ it( 'should return the default value if the nested value is undefined', () => {
+ const input = { x: { y: { z: undefined } } };
+ const result = getNestedValue( input, [ 'x', 'y', 'z' ], 456 );
+
+ expect( result ).toEqual( 456 );
+ } );
+
+ it( 'should return the nested value if it is different to undefined', () => {
+ const input = { x: { y: { z: null } } };
+ const result = getNestedValue( input, 'x.y.z', 456 );
+
+ expect( result ).toBeNull();
+ } );
+
+ it( 'should return the default value if the nested value does not exist', () => {
+ const input = { x: { y: { z: 123 } } };
+ const result = getNestedValue( input, [ 'x', 'y', 'z1' ], 456 );
+
+ expect( result ).toEqual( 456 );
+ } );
+
+ it( 'should return undefined if the nested value does not exist', () => {
+ const input = { x: { y: { z: 123 } } };
+ const result = getNestedValue( input, [ 'x', 'y', 'z1' ] );
+
+ expect( result ).toBeUndefined();
+ } );
+} );
diff --git a/packages/core-data/src/utils/test/set-nested-value.js b/packages/core-data/src/utils/test/set-nested-value.js
index bbc71291807644..7785e94830109a 100644
--- a/packages/core-data/src/utils/test/set-nested-value.js
+++ b/packages/core-data/src/utils/test/set-nested-value.js
@@ -19,6 +19,13 @@ describe( 'setNestedValue', () => {
expect( result ).toEqual( { x: { y: { z: 456 } } } );
} );
+ it( 'should set values at deep level having a string as path', () => {
+ const input = { x: { y: { z: 123 } } };
+ const result = setNestedValue( input, 'x.y.z', 456 );
+
+ expect( result ).toEqual( { x: { y: { z: 456 } } } );
+ } );
+
it( 'should create nested objects if necessary', () => {
const result = setNestedValue( {}, [ 'x', 'y', 'z' ], 123 );
From 18fe8d83f55bc2261d2df77a09cb70ca7e02c204 Mon Sep 17 00:00:00 2001
From: tellthemachines
Date: Wed, 13 Sep 2023 12:59:55 +1000
Subject: [PATCH 39/42] Try using fallback layout instead of default in post
editor (#54371)
* Try using fallback layout instead of default in post editor
* Check whether Post Content is at root level before resorting to fallback layout
---
.../src/components/visual-editor/index.js | 36 ++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js
index a35f49c37b9c17..14b6bf475045e1 100644
--- a/packages/edit-post/src/components/visual-editor/index.js
+++ b/packages/edit-post/src/components/visual-editor/index.js
@@ -66,6 +66,15 @@ function getPostContentAttributes( blocks ) {
}
}
+function checkForPostContentAtRootLevel( blocks ) {
+ for ( let i = 0; i < blocks.length; i++ ) {
+ if ( blocks[ i ].name === 'core/post-content' ) {
+ return true;
+ }
+ }
+ return false;
+}
+
export default function VisualEditor( { styles } ) {
const {
deviceType,
@@ -221,6 +230,26 @@ export default function VisualEditor( { styles } ) {
postContentAttributes,
] );
+ const hasPostContentAtRootLevel = useMemo( () => {
+ if ( ! editedPostTemplate?.content && ! editedPostTemplate?.blocks ) {
+ return false;
+ }
+ // When in template editing mode, we can access the blocks directly.
+ if ( editedPostTemplate?.blocks ) {
+ return checkForPostContentAtRootLevel( editedPostTemplate?.blocks );
+ }
+ // If there are no blocks, we have to parse the content string.
+ // Best double-check it's a string otherwise the parse function gets unhappy.
+ const parseableContent =
+ typeof editedPostTemplate?.content === 'string'
+ ? editedPostTemplate?.content
+ : '';
+
+ return (
+ checkForPostContentAtRootLevel( parse( parseableContent ) ) || false
+ );
+ }, [ editedPostTemplate?.content, editedPostTemplate?.blocks ] );
+
const { layout = {}, align = '' } = newestPostContentAttributes || {};
const postContentLayoutClasses = useLayoutClasses(
@@ -265,6 +294,11 @@ export default function VisualEditor( { styles } ) {
? postContentLayout
: fallbackLayout;
+ const postEditorLayout =
+ blockListLayout?.type === 'default' && ! hasPostContentAtRootLevel
+ ? fallbackLayout
+ : blockListLayout;
+
const observeTypingRef = useTypingObserver();
const titleRef = useRef();
useEffect( () => {
@@ -337,7 +371,7 @@ export default function VisualEditor( { styles } ) {
/>
{ align && (
From 4efbe2c07e071d83867366469782e766829f5fbe Mon Sep 17 00:00:00 2001
From: tellthemachines
Date: Wed, 13 Sep 2023 13:00:55 +1000
Subject: [PATCH 40/42] Don't output root padding when editing patterns.
(#54373)
---
.../global-styles/use-global-styles-output.js | 25 +++++++++++++------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js
index eccbb4652072a8..e8cdd332e9cce6 100644
--- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js
+++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js
@@ -32,6 +32,7 @@ import {
kebabCase,
setImmutably,
} from '../../utils/object';
+import BlockContext from '../block-context';
// List of block support features that can have their related styles
// generated under their own feature level selector rather than the block's.
@@ -307,13 +308,15 @@ const getFeatureDeclarations = ( selectors, styles ) => {
*
* @param {Object} tree A theme.json tree containing layout definitions.
*
+ * @param {boolean} isTemplate Whether the entity being edited is a full template or a pattern.
* @return {Array} An array of style declarations.
*/
export function getStylesDeclarations(
blockStyles = {},
selector = '',
useRootPaddingAlign,
- tree = {}
+ tree = {},
+ isTemplate = true
) {
const isRoot = ROOT_BLOCK_SELECTOR === selector;
const output = Object.entries( STYLE_PROPERTY ).reduce(
@@ -386,10 +389,10 @@ export function getStylesDeclarations(
// This is temporary as we absorb more and more styles into the engine.
const extraRules = getCSSRules( blockStyles );
extraRules.forEach( ( rule ) => {
- // Don't output padding properties if padding variables are set.
+ // Don't output padding properties if padding variables are set or if we're not editing a full template.
if (
isRoot &&
- useRootPaddingAlign &&
+ ( useRootPaddingAlign || ! isTemplate ) &&
rule.key.startsWith( 'padding' )
) {
return;
@@ -757,7 +760,8 @@ export const toStyles = (
blockSelectors,
hasBlockGapSupport,
hasFallbackGapSupport,
- disableLayoutStyles = false
+ disableLayoutStyles = false,
+ isTemplate = true
) => {
const nodesWithStyles = getNodesWithStyles( tree, blockSelectors );
const nodesWithSettings = getNodesWithSettings( tree, blockSelectors );
@@ -782,7 +786,8 @@ export const toStyles = (
ruleset += ` --wp--style--global--wide-size: ${ wideSize };`;
}
- if ( useRootPaddingAlign ) {
+ // Root padding styles should only be output for full templates, not patterns or template parts.
+ if ( useRootPaddingAlign && isTemplate ) {
/*
* These rules reproduce the ones from https://github.com/WordPress/gutenberg/blob/79103f124925d1f457f627e154f52a56228ed5ad/lib/class-wp-theme-json-gutenberg.php#L2508
* almost exactly, but for the selectors that target block wrappers in the front end. This code only runs in the editor, so it doesn't need those selectors.
@@ -909,7 +914,8 @@ export const toStyles = (
styles,
selector,
useRootPaddingAlign,
- tree
+ tree,
+ isTemplate
);
if ( declarations?.length ) {
ruleset += `${ selector }{${ declarations.join( ';' ) };}`;
@@ -1152,6 +1158,10 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) {
return !! getSettings().disableLayoutStyles;
} );
+ const blockContext = useContext( BlockContext );
+
+ const isTemplate = blockContext?.templateSlug !== undefined;
+
const getBlockStyles = useSelect( ( select ) => {
return select( blocksStore ).getBlockStyles;
}, [] );
@@ -1176,7 +1186,8 @@ export function useGlobalStylesOutputWithConfig( mergedConfig = {} ) {
blockSelectors,
hasBlockGapSupport,
hasFallbackGapSupport,
- disableLayoutStyles
+ disableLayoutStyles,
+ isTemplate
);
const svgs = toSvgFilters( mergedConfig, blockSelectors );
From 0ffb5963dfdce7fc161b1c8b7d291ae43141b34f Mon Sep 17 00:00:00 2001
From: Matias Benedetto
Date: Wed, 13 Sep 2023 00:03:55 -0300
Subject: [PATCH 41/42] Export ProgressBar to allow it to be used (#54404)
* Export ProgressBar to allow it to be used
* add changelog entry
---
packages/components/CHANGELOG.md | 1 +
packages/components/src/index.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 5426a4d6d6dda0..735e3910b0520f 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,7 @@
## Unreleased
- `BorderControl`: Apply proper metrics and simpler text ([#53998](https://github.com/WordPress/gutenberg/pull/53998)).
+- `ProgressBar`: Export component to allow it to be used ([#54404](https://github.com/WordPress/gutenberg/pull/54404)).
### Enhancements
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index a824162cb24129..85ce7b14fffaec 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -132,6 +132,7 @@ export { default as PanelHeader } from './panel/header';
export { default as PanelRow } from './panel/row';
export { default as Placeholder } from './placeholder';
export { default as Popover } from './popover';
+export { default as __experimentalProgressBar } from './progress-bar';
export { default as QueryControls } from './query-controls';
export { default as __experimentalRadio } from './radio-group/radio';
export { default as __experimentalRadioGroup } from './radio-group';
From b05bee7e2e8eefb2be06c228002e4f9783847747 Mon Sep 17 00:00:00 2001
From: Ari Stathopoulos
Date: Wed, 13 Sep 2023 10:03:06 +0300
Subject: [PATCH 42/42] Update WPCS to v3.0, and fix all reported Coding
Standards issues (#53866)
* update composer.json
* Remove invalid rule
* exclude the build folder
* ran phpcbf in the lib folder
* more fixes
* fix assignments in condition
* cleanup composer.json file
* use do-while instead of while(true)
* Update phpunit/experimental/interactivity-api/directive-processing-test.php
Co-authored-by: Juliette <663378+jrfnl@users.noreply.github.com>
* add a rule in the config file instead of ignoring inside the files
* update ruleset for textdomain
* revert opening php tags
* Ignore empty files in tests/gutenberg-test-themes
* ruleset cleanup for parser.php
* More fixes after rebasing the PR
* fix alignment of param docs
---------
Co-authored-by: Juliette <663378+jrfnl@users.noreply.github.com>
---
composer.json | 4 +--
lib/block-supports/border.php | 12 ++++----
lib/block-supports/elements.php | 2 +-
lib/block-supports/layout.php | 2 +-
lib/block-supports/typography.php | 1 -
lib/blocks.php | 2 +-
lib/class-wp-duotone-gutenberg.php | 4 +--
lib/class-wp-theme-json-data-gutenberg.php | 1 -
lib/class-wp-theme-json-gutenberg.php | 13 ++++-----
...class-wp-theme-json-resolver-gutenberg.php | 12 ++++----
lib/compat/wordpress-6.3/blocks.php | 2 +-
.../class-gutenberg-navigation-fallback.php | 2 +-
...class-gutenberg-html-tag-processor-6-3.php | 18 +++++-------
lib/compat/wordpress-6.3/rest-api.php | 6 ++--
lib/compat/wordpress-6.3/script-loader.php | 2 +-
lib/compat/wordpress-6.4/fonts/fonts.php | 2 +-
lib/compat/wordpress-6.4/script-loader.php | 2 +-
lib/experimental/block-hooks.php | 10 +++----
lib/experimental/blocks.php | 4 +--
.../class-wp-rest-customizer-nonces.php | 1 -
.../fonts-api/bc-layer/class-wp-web-fonts.php | 12 ++++----
lib/experimental/fonts-api/class-wp-fonts.php | 12 ++++----
lib/experimental/fonts-api/fonts-api.php | 4 +--
.../font-library/class-wp-font-family.php | 2 +-
.../class-wp-directive-context.php | 2 +-
.../class-wp-directive-processor.php | 6 ++--
.../class-wp-interactivity-store.php | 8 ++---
.../directive-processing.php | 2 +-
lib/experimental/kses.php | 1 -
lib/experimental/navigation-theme-opt-in.php | 2 +-
.../src/comment-template/index.php | 2 +-
.../src/comments-pagination-next/index.php | 2 +-
.../comments-pagination-previous/index.php | 2 +-
.../block-library/src/latest-posts/index.php | 2 +-
.../block-library/src/navigation/index.php | 8 ++---
.../block-library/src/post-excerpt/index.php | 4 +--
.../block-library/src/post-template/index.php | 2 +-
.../src/query-pagination-next/index.php | 2 +-
.../src/query-pagination-previous/index.php | 2 +-
packages/block-library/src/search/index.php | 2 +-
.../mu-plugins/enable-templates-ui.php | 2 +-
.../e2e-tests/mu-plugins/nocache-headers.php | 2 +-
packages/e2e-tests/plugins/block-context.php | 2 +-
packages/e2e-tests/plugins/iframed-block.php | 4 +--
.../plugins/iframed-enqueue-block-assets.php | 2 +-
.../iframed-enqueue-block-editor-settings.php | 2 +-
.../plugins/iframed-inline-styles.php | 6 ++--
.../plugins/iframed-masonry-block.php | 4 +--
.../plugins/iframed-multiple-stylesheets.php | 4 +--
.../e2e-tests/plugins/interactive-blocks.php | 7 ++---
.../plugins/marquee-function-widget.php | 4 +--
phpcs.xml.dist | 29 ++++++++++---------
.../render-block-navigation-submenu-test.php | 1 -
.../blocks/render-block-navigation-test.php | 1 -
.../blocks/render-comment-template-test.php | 4 +--
phpunit/blocks/render-post-template-test.php | 2 +-
phpunit/class-block-context-test.php | 8 ++---
...ass-block-library-navigation-link-test.php | 2 +-
...erg-navigation-fallback-gutenberg-test.php | 3 --
...st-navigation-fallback-controller-test.php | 1 -
.../class-phpunit-environment-setup-test.php | 1 -
phpunit/class-wp-theme-json-resolver-test.php | 11 ++++---
phpunit/class-wp-theme-json-test.php | 4 ---
.../directive-processing-test.php | 15 ++++++----
.../directives/wp-context-test.php | 1 -
phpunit/l10n-test.php | 10 +++----
phpunit/tests/fonts-api/base.php | 22 +++++++-------
phpunit/tests/fonts-api/wpFonts/add.php | 1 -
.../tests/fonts-api/wpFonts/addFontFamily.php | 1 -
phpunit/tests/fonts-api/wpFonts/doItem.php | 2 +-
phpunit/tests/fonts-api/wpFonts/doItems.php | 2 +-
.../fonts-api/wpFonts/registerProvider.php | 12 ++++----
.../registerFontsFromThemeJson.php | 2 +-
.../fonts-api/wpRegisterFontProvider.php | 17 +++++------
phpunit/tests/fonts/font-face/base.php | 18 ++++++------
.../wpFontLibrary/getFontCollection.php | 1 -
.../getFontCollection.php | 1 -
.../getFontCollections.php | 1 -
.../installFonts.php | 2 --
.../registerRoutes.php | 1 -
.../uninstallFonts.php | 3 --
test/emptytheme/functions.php | 19 ++++++------
.../emptyhybrid/functions.php | 18 +++++++-----
83 files changed, 204 insertions(+), 232 deletions(-)
diff --git a/composer.json b/composer.json
index 134e366befdb94..3571377bd58bd7 100644
--- a/composer.json
+++ b/composer.json
@@ -26,10 +26,8 @@
}
},
"require-dev": {
- "dealerdirect/phpcodesniffer-composer-installer": "^0.7",
- "squizlabs/php_codesniffer": "^3.5",
"phpcompatibility/phpcompatibility-wp": "^2.1.3",
- "wp-coding-standards/wpcs": "^2.2",
+ "wp-coding-standards/wpcs": "^3.0",
"sirbrillig/phpcs-variable-analysis": "^2.8",
"spatie/phpunit-watcher": "^1.23",
"yoast/phpunit-polyfills": "^1.0",
diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php
index 486f6d99b64890..51e484c43af421 100644
--- a/lib/block-supports/border.php
+++ b/lib/block-supports/border.php
@@ -134,24 +134,24 @@ function gutenberg_apply_border_support( $block_type, $block_attributes ) {
* flag nested under `experimentalBorder` must be enabled for the feature
* to be opted into.
*
- * @param WP_Block_Type $block_type Block type to check for support.
- * @param string $feature Name of the feature to check support for.
- * @param mixed $default Fallback value for feature support, defaults to false.
+ * @param WP_Block_Type $block_type Block type to check for support.
+ * @param string $feature Name of the feature to check support for.
+ * @param mixed $default_value Fallback value for feature support, defaults to false.
*
* @return boolean Whether or not the feature is supported.
*/
-function gutenberg_has_border_feature_support( $block_type, $feature, $default = false ) {
+function gutenberg_has_border_feature_support( $block_type, $feature, $default_value = false ) {
// Check if all border support features have been opted into via `"__experimentalBorder": true`.
if (
property_exists( $block_type, 'supports' ) &&
- ( true === _wp_array_get( $block_type->supports, array( '__experimentalBorder' ), $default ) )
+ ( true === _wp_array_get( $block_type->supports, array( '__experimentalBorder' ), $default_value ) )
) {
return true;
}
// Check if the specific feature has been opted into individually
// via nested flag under `__experimentalBorder`.
- return block_has_support( $block_type, array( '__experimentalBorder', $feature ), $default );
+ return block_has_support( $block_type, array( '__experimentalBorder', $feature ), $default_value );
}
// Register the block support.
diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php
index 328d371c3e4fd8..2106360c4bd047 100644
--- a/lib/block-supports/elements.php
+++ b/lib/block-supports/elements.php
@@ -80,7 +80,7 @@ function gutenberg_render_elements_support( $block_content, $block ) {
foreach ( $element_config['paths'] as $path ) {
if ( null !== _wp_array_get( $block['attrs'], explode( '.', $path ), null ) ) {
- $element_colors_set++;
+ ++$element_colors_set;
}
}
}
diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php
index 0cf501028efd88..3acef1f7df4d4b 100644
--- a/lib/block-supports/layout.php
+++ b/lib/block-supports/layout.php
@@ -833,7 +833,7 @@ function gutenberg_restore_group_inner_container( $block_content, $block ) {
);
$updated_content = preg_replace_callback(
$replace_regex,
- static function( $matches ) {
+ static function ( $matches ) {
return $matches[1] . '' . $matches[2] . '
' . $matches[3];
},
$block_content
diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php
index d0721503fb101e..1e8c894387de62 100644
--- a/lib/block-supports/typography.php
+++ b/lib/block-supports/typography.php
@@ -244,7 +244,6 @@ function gutenberg_render_typography_support( $block_content, $block ) {
}
return $block_content;
-
}
/**
diff --git a/lib/blocks.php b/lib/blocks.php
index 34ef63f2e87ad9..c54f5df68489ae 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -274,7 +274,7 @@ function gutenberg_register_core_block_assets( $block_name ) {
if ( ! $stylesheet_removed ) {
add_action(
'wp_enqueue_scripts',
- static function() {
+ static function () {
wp_dequeue_style( 'wp-block-library-theme' );
}
);
diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php
index 41120a882ed235..5c4b4a3cc9d9c7 100644
--- a/lib/class-wp-duotone-gutenberg.php
+++ b/lib/class-wp-duotone-gutenberg.php
@@ -482,7 +482,7 @@ private static function is_preset( $duotone_attr ) {
* @return string The CSS variable name.
*/
private static function get_css_custom_property_name( $slug ) {
- return "--wp--preset--duotone--$slug";
+ return "--wp--preset--duotone--$slug";
}
/**
@@ -492,7 +492,7 @@ private static function get_css_custom_property_name( $slug ) {
* @return string The ID of the duotone filter.
*/
private static function get_filter_id( $slug ) {
- return "wp-duotone-$slug";
+ return "wp-duotone-$slug";
}
/**
diff --git a/lib/class-wp-theme-json-data-gutenberg.php b/lib/class-wp-theme-json-data-gutenberg.php
index db0737ebea08b6..f0d2025347493b 100644
--- a/lib/class-wp-theme-json-data-gutenberg.php
+++ b/lib/class-wp-theme-json-data-gutenberg.php
@@ -72,5 +72,4 @@ public function update_with( $new_data ) {
public function get_data() {
return $this->theme_json->get_raw_data();
}
-
}
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 63de99624a0ac6..7aa165ecc06353 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -2345,7 +2345,7 @@ public function get_styles_for_block( $block_metadata ) {
// Prepend the variation selector to the current selector.
$split_selectors = explode( ',', $shortened_selector );
$updated_selectors = array_map(
- static function( $split_selector ) use ( $clean_style_variation_selector ) {
+ static function ( $split_selector ) use ( $clean_style_variation_selector ) {
return $clean_style_variation_selector . $split_selector;
},
$split_selectors
@@ -2384,7 +2384,7 @@ static function( $split_selector ) use ( $clean_style_variation_selector ) {
$pseudo_matches = array_values(
array_filter(
$element_pseudo_allowed,
- static function( $pseudo_selector ) use ( $selector ) {
+ static function ( $pseudo_selector ) use ( $selector ) {
return str_contains( $selector, $pseudo_selector );
}
)
@@ -3415,7 +3415,7 @@ public function set_spacing_sizes() {
}
if ( $below_midpoint_count < $steps_mid_point - 2 ) {
- $x_small_count++;
+ ++$x_small_count;
}
$slug -= 10;
@@ -3452,7 +3452,7 @@ public function set_spacing_sizes() {
}
if ( $above_midpoint_count > 1 ) {
- $x_large_count++;
+ ++$x_large_count;
}
$slug += 10;
@@ -3728,10 +3728,10 @@ public static function resolve_variables( $theme_json ) {
$theme_vars = static::compute_theme_vars( $settings );
$vars = array_reduce(
array_merge( $preset_vars, $theme_vars ),
- function( $carry, $item ) {
+ function ( $carry, $item ) {
$name = $item['name'];
$carry[ "var({$name})" ] = $item['value'];
- return $carry;
+ return $carry;
},
array()
);
@@ -3739,5 +3739,4 @@ function( $carry, $item ) {
$theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars );
return $theme_json;
}
-
}
diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php
index c5a4c662221aeb..363c25f63761cb 100644
--- a/lib/class-wp-theme-json-resolver-gutenberg.php
+++ b/lib/class-wp-theme-json-resolver-gutenberg.php
@@ -408,18 +408,18 @@ public static function get_block_data() {
/**
* When given an array, this will remove any keys with the name `//`.
*
- * @param array $array The array to filter.
+ * @param array $json_array The array to filter.
* @return array The filtered array.
*/
- private static function remove_json_comments( $array ) {
- unset( $array['//'] );
- foreach ( $array as $k => $v ) {
+ private static function remove_json_comments( $json_array ) {
+ unset( $json_array['//'] );
+ foreach ( $json_array as $k => $v ) {
if ( is_array( $v ) ) {
- $array[ $k ] = static::remove_json_comments( $v );
+ $json_array[ $k ] = static::remove_json_comments( $v );
}
}
- return $array;
+ return $json_array;
}
/**
diff --git a/lib/compat/wordpress-6.3/blocks.php b/lib/compat/wordpress-6.3/blocks.php
index 962c9ee3e07ef3..001416b42566f7 100644
--- a/lib/compat/wordpress-6.3/blocks.php
+++ b/lib/compat/wordpress-6.3/blocks.php
@@ -104,7 +104,7 @@ function gutenberg_wp_block_register_post_meta() {
$post_type,
'wp_pattern_sync_status',
array(
- 'auth_callback' => function() {
+ 'auth_callback' => function () {
return current_user_can( 'edit_posts' );
},
'sanitize_callback' => 'sanitize_text_field',
diff --git a/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php b/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php
index fcf6e13b0954d7..fcd70da61f57ed 100644
--- a/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php
+++ b/lib/compat/wordpress-6.3/class-gutenberg-navigation-fallback.php
@@ -154,7 +154,7 @@ private static function get_fallback_classic_menu() {
private static function get_most_recently_created_nav_menu( $classic_nav_menus ) {
usort(
$classic_nav_menus,
- static function( $a, $b ) {
+ static function ( $a, $b ) {
return $b->term_id - $a->term_id;
}
);
diff --git a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php
index b869e2ef1e1c16..73cdb9342490c1 100644
--- a/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php
+++ b/lib/compat/wordpress-6.3/html-api/class-gutenberg-html-tag-processor-6-3.php
@@ -947,7 +947,7 @@ private function parse_next_tag() {
if ( '/' === $this->html[ $at + 1 ] ) {
$this->is_closing_tag = true;
- $at++;
+ ++$at;
} else {
$this->is_closing_tag = false;
}
@@ -1016,7 +1016,7 @@ private function parse_next_tag() {
*
* See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment
*/
- $closer_at--; // Pre-increment inside condition below reduces risk of accidental infinite looping.
+ --$closer_at; // Pre-increment inside condition below reduces risk of accidental infinite looping.
while ( ++$closer_at < strlen( $html ) ) {
$closer_at = strpos( $html, '--', $closer_at );
if ( false === $closer_at ) {
@@ -1097,7 +1097,7 @@ private function parse_next_tag() {
* See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name
*/
if ( '>' === $html[ $at + 1 ] ) {
- $at++;
+ ++$at;
continue;
}
@@ -1739,7 +1739,7 @@ public function get_attribute( $name ) {
* @param string $prefix Prefix of requested attribute names.
* @return array|null List of attribute names, or `null` when no tag opener is matched.
*/
- function get_attribute_names_with_prefix( $prefix ) {
+ public function get_attribute_names_with_prefix( $prefix ) {
if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) {
return null;
}
@@ -2282,11 +2282,9 @@ private function matches() {
* See https://html.spec.whatwg.org/#attributes-3
* See https://html.spec.whatwg.org/#space-separated-tokens
*/
- while (
- // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
- false !== ( $class_at = strpos( $this->html, $this->sought_class_name, $class_at ) ) &&
- $class_at < $class_end
- ) {
+ do {
+ $class_at = strpos( $this->html, $this->sought_class_name, $class_at );
+
/*
* Verify this class starts at a boundary.
*/
@@ -2312,7 +2310,7 @@ private function matches() {
}
return true;
- }
+ } while ( false !== $class_at && $class_at < $class_end );
return false;
}
diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php
index 041398eda24b4f..db90a2575d9eea 100644
--- a/lib/compat/wordpress-6.3/rest-api.php
+++ b/lib/compat/wordpress-6.3/rest-api.php
@@ -70,9 +70,9 @@ function add_modified_wp_template_schema() {
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
- 'get_callback' => function( $object ) {
- if ( ! empty( $object['wp_id'] ) ) {
- $post = get_post( $object['wp_id'] );
+ 'get_callback' => function ( $template_object ) {
+ if ( ! empty( $template_object['wp_id'] ) ) {
+ $post = get_post( $template_object['wp_id'] );
if ( $post && isset( $post->post_modified ) ) {
return mysql_to_rfc3339( $post->post_modified );
}
diff --git a/lib/compat/wordpress-6.3/script-loader.php b/lib/compat/wordpress-6.3/script-loader.php
index 8f7bda2a648114..89149f3a753140 100644
--- a/lib/compat/wordpress-6.3/script-loader.php
+++ b/lib/compat/wordpress-6.3/script-loader.php
@@ -100,7 +100,7 @@ function _gutenberg_get_iframed_editor_assets() {
add_filter(
'block_editor_settings_all',
- static function( $settings ) {
+ static function ( $settings ) {
// We must override what core is passing now.
$settings['__unstableResolvedAssets'] = _gutenberg_get_iframed_editor_assets();
return $settings;
diff --git a/lib/compat/wordpress-6.4/fonts/fonts.php b/lib/compat/wordpress-6.4/fonts/fonts.php
index 2e06b6e085d907..ca930141b0c6db 100644
--- a/lib/compat/wordpress-6.4/fonts/fonts.php
+++ b/lib/compat/wordpress-6.4/fonts/fonts.php
@@ -39,7 +39,7 @@ function wp_print_font_faces( $fonts = array() ) {
// @core-merge: do not merge this code into Core.
add_filter(
'block_editor_settings_all',
- static function( $settings ) {
+ static function ( $settings ) {
ob_start();
// @core-merge: add only this line into Core's `_wp_get_iframed_editor_assets()` function after `wp_print_styles()`.
wp_print_font_faces();
diff --git a/lib/compat/wordpress-6.4/script-loader.php b/lib/compat/wordpress-6.4/script-loader.php
index 8be3084f9e0b67..0f90494927c725 100644
--- a/lib/compat/wordpress-6.4/script-loader.php
+++ b/lib/compat/wordpress-6.4/script-loader.php
@@ -179,7 +179,7 @@ function _gutenberg_get_iframed_editor_assets_6_4() {
add_filter(
'block_editor_settings_all',
- static function( $settings ) {
+ static function ( $settings ) {
// We must override what core is passing now.
$settings['__unstableResolvedAssets'] = _gutenberg_get_iframed_editor_assets_6_4();
return $settings;
diff --git a/lib/experimental/block-hooks.php b/lib/experimental/block-hooks.php
index abe85ee3b9ace8..0586b989608133 100644
--- a/lib/experimental/block-hooks.php
+++ b/lib/experimental/block-hooks.php
@@ -152,7 +152,7 @@ function gutenberg_add_hooked_block( $hooked_block, $position, $anchor_block ) {
* @return callable A function that accepts a block's content and returns the content with the inserted block.
*/
function gutenberg_insert_hooked_block( $inserted_block, $relative_position, $anchor_block_type ) {
- return function( $block ) use ( $inserted_block, $relative_position, $anchor_block_type ) {
+ return function ( $block ) use ( $inserted_block, $relative_position, $anchor_block_type ) {
if ( $anchor_block_type === $block['blockName'] ) {
if ( 'first_child' === $relative_position ) {
array_unshift( $block['innerBlocks'], $inserted_block );
@@ -173,7 +173,7 @@ function gutenberg_insert_hooked_block( $inserted_block, $relative_position, $an
$anchor_block_index = array_search( $anchor_block_type, array_column( $block['innerBlocks'], 'blockName' ), true );
if ( false !== $anchor_block_index && ( 'after' === $relative_position || 'before' === $relative_position ) ) {
if ( 'after' === $relative_position ) {
- $anchor_block_index++;
+ ++$anchor_block_index;
}
array_splice( $block['innerBlocks'], $anchor_block_index, 0, array( $inserted_block ) );
@@ -181,9 +181,9 @@ function gutenberg_insert_hooked_block( $inserted_block, $relative_position, $an
$chunk_index = 0;
while ( $anchor_block_index > 0 ) {
if ( ! is_string( $block['innerContent'][ $chunk_index ] ) ) {
- $anchor_block_index--;
+ --$anchor_block_index;
}
- $chunk_index++;
+ ++$chunk_index;
}
// Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`)
// when rendering blocks, we also need to insert a value (`null`, to mark a block
@@ -204,7 +204,7 @@ function gutenberg_insert_hooked_block( $inserted_block, $relative_position, $an
* @return callable A filter for the `rest_prepare_block_type` hook that adds a `block_hooks` field to the network response.
*/
function gutenberg_add_block_hooks_field_to_block_type_controller( $inserted_block_type, $position, $anchor_block_type ) {
- return function( $response, $block_type ) use ( $inserted_block_type, $position, $anchor_block_type ) {
+ return function ( $response, $block_type ) use ( $inserted_block_type, $position, $anchor_block_type ) {
if ( $block_type->name !== $inserted_block_type ) {
return $response;
}
diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php
index 5ab0d093758124..e509c889a9ef8a 100644
--- a/lib/experimental/blocks.php
+++ b/lib/experimental/blocks.php
@@ -42,7 +42,7 @@ function wp_enqueue_block_view_script( $block_name, $args ) {
* is to ensure the content exists.
* @return string Block content.
*/
- $callback = static function( $content, $block ) use ( $args, $block_name ) {
+ $callback = static function ( $content, $block ) use ( $args, $block_name ) {
// Sanity check.
if ( empty( $block['blockName'] ) || $block_name !== $block['blockName'] ) {
@@ -185,7 +185,7 @@ function gutenberg_render_block_connections( $block_content, $block, $block_inst
);
if ( ! $found ) {
return $block_content;
- };
+ }
$tag_name = $tags->get_tag();
$markup = "<$tag_name>$custom_value$tag_name>";
$updated_tags = new WP_HTML_Tag_Processor( $markup );
diff --git a/lib/experimental/class-wp-rest-customizer-nonces.php b/lib/experimental/class-wp-rest-customizer-nonces.php
index 15c587295557d8..67be9485df29f4 100644
--- a/lib/experimental/class-wp-rest-customizer-nonces.php
+++ b/lib/experimental/class-wp-rest-customizer-nonces.php
@@ -72,5 +72,4 @@ public function get_save_nonce() {
'stylesheet' => $wp_customize->get_stylesheet(),
);
}
-
}
diff --git a/lib/experimental/fonts-api/bc-layer/class-wp-web-fonts.php b/lib/experimental/fonts-api/bc-layer/class-wp-web-fonts.php
index 435895ef9489c9..550940935b7dbb 100644
--- a/lib/experimental/fonts-api/bc-layer/class-wp-web-fonts.php
+++ b/lib/experimental/fonts-api/bc-layer/class-wp-web-fonts.php
@@ -65,8 +65,8 @@ public function __construct() {
* @type string $provider_id => array {
* An associate array of provider's class name and fonts.
*
- * @type string $class Fully qualified name of the provider's class.
- * @type string[] $fonts An array of enqueued font handles for this provider.
+ * @type string $class_name Fully qualified name of the provider's class.
+ * @type string[] $fonts An array of enqueued font handles for this provider.
* }
* }
*/
@@ -80,16 +80,16 @@ public function get_providers() {
* @since X.X.X
*
* @param string $provider_id The provider's unique ID.
- * @param string $class The provider class name.
+ * @param string $class_name The provider class name.
* @return bool True if successfully registered, else false.
*/
- public function register_provider( $provider_id, $class ) {
- if ( empty( $provider_id ) || empty( $class ) || ! class_exists( $class ) ) {
+ public function register_provider( $provider_id, $class_name ) {
+ if ( empty( $provider_id ) || empty( $class_name ) || ! class_exists( $class_name ) ) {
return false;
}
$this->providers[ $provider_id ] = array(
- 'class' => $class,
+ 'class' => $class_name,
'fonts' => array(),
);
return true;
diff --git a/lib/experimental/fonts-api/class-wp-fonts.php b/lib/experimental/fonts-api/class-wp-fonts.php
index 5ce85bab315094..b6d965d5bbc5e6 100644
--- a/lib/experimental/fonts-api/class-wp-fonts.php
+++ b/lib/experimental/fonts-api/class-wp-fonts.php
@@ -115,8 +115,8 @@ public function __construct() {
* @type string $provider_id => array {
* An associate array of provider's class name and fonts.
*
- * @type string $class Fully qualified name of the provider's class.
- * @type string[] $fonts An array of enqueued font handles for this provider.
+ * @type string $class_name Fully qualified name of the provider's class.
+ * @type string[] $fonts An array of enqueued font handles for this provider.
* }
* }
*/
@@ -130,16 +130,16 @@ public function get_providers() {
* @since X.X.X
*
* @param string $provider_id The provider's unique ID.
- * @param string $class The provider class name.
+ * @param string $class_name The provider class name.
* @return bool True if successfully registered, else false.
*/
- public function register_provider( $provider_id, $class ) {
- if ( empty( $provider_id ) || empty( $class ) || ! class_exists( $class ) ) {
+ public function register_provider( $provider_id, $class_name ) {
+ if ( empty( $provider_id ) || empty( $class_name ) || ! class_exists( $class_name ) ) {
return false;
}
$this->providers[ $provider_id ] = array(
- 'class' => $class,
+ 'class' => $class_name,
'fonts' => array(),
);
return true;
diff --git a/lib/experimental/fonts-api/fonts-api.php b/lib/experimental/fonts-api/fonts-api.php
index 8d07dc118f56e1..2ff0ff07208d77 100644
--- a/lib/experimental/fonts-api/fonts-api.php
+++ b/lib/experimental/fonts-api/fonts-api.php
@@ -223,7 +223,7 @@ function wp_print_fonts( $handles = false ) {
*/
add_filter(
'mime_types',
- static function( $mime_types ) {
+ static function ( $mime_types ) {
// Webfonts formats.
$mime_types['woff2'] = 'font/woff2';
$mime_types['woff'] = 'font/woff';
@@ -246,7 +246,7 @@ static function( $mime_types ) {
add_filter(
'block_editor_settings_all',
- static function( $settings ) {
+ static function ( $settings ) {
ob_start();
wp_print_fonts( true );
$styles = ob_get_clean();
diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php
index 76ffcd402ac9d3..cacf504382cce8 100644
--- a/lib/experimental/fonts/font-library/class-wp-font-family.php
+++ b/lib/experimental/fonts/font-library/class-wp-font-family.php
@@ -182,7 +182,7 @@ private function get_upload_overrides( $filename ) {
// Seems mime type for files that are not images cannot be tested.
// See wp_check_filetype_and_ext().
'test_type' => false,
- 'unique_filename_callback' => static function() use ( $filename ) {
+ 'unique_filename_callback' => static function () use ( $filename ) {
// Keep the original filename.
return $filename;
},
diff --git a/lib/experimental/interactivity-api/class-wp-directive-context.php b/lib/experimental/interactivity-api/class-wp-directive-context.php
index 6e28db83509f52..3fc47b26043167 100644
--- a/lib/experimental/interactivity-api/class-wp-directive-context.php
+++ b/lib/experimental/interactivity-api/class-wp-directive-context.php
@@ -44,7 +44,7 @@ class WP_Directive_Context {
*
* @param array $context A context.
*/
- function __construct( $context = array() ) {
+ public function __construct( $context = array() ) {
$this->set_context( $context );
}
diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php
index e57000b3663ce4..807a02724197c1 100644
--- a/lib/experimental/interactivity-api/class-wp-directive-processor.php
+++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php
@@ -27,7 +27,7 @@ class WP_Directive_Processor extends WP_HTML_Tag_Processor {
*
* @var array
*/
- static $root_blocks = array();
+ public static $root_blocks = array();
/**
* Add a root block to the list.
@@ -78,7 +78,7 @@ public function next_balanced_closer() {
)
) ) {
if ( ! $this->is_tag_closer() ) {
- $depth++;
+ ++$depth;
continue;
}
@@ -86,7 +86,7 @@ public function next_balanced_closer() {
return true;
}
- $depth--;
+ --$depth;
}
return false;
diff --git a/lib/experimental/interactivity-api/class-wp-interactivity-store.php b/lib/experimental/interactivity-api/class-wp-interactivity-store.php
index 0dd8aae5406fc0..89cb58700554a9 100644
--- a/lib/experimental/interactivity-api/class-wp-interactivity-store.php
+++ b/lib/experimental/interactivity-api/class-wp-interactivity-store.php
@@ -34,7 +34,7 @@ class WP_Interactivity_Store {
*
* @return array
*/
- static function get_data() {
+ public static function get_data() {
return self::$store;
}
@@ -43,21 +43,21 @@ static function get_data() {
*
* @param array $data The data that will be merged with the existing store.
*/
- static function merge_data( $data ) {
+ public static function merge_data( $data ) {
self::$store = array_replace_recursive( self::$store, $data );
}
/**
* Reset the store data.
*/
- static function reset() {
+ public static function reset() {
self::$store = array();
}
/**
* Render the store data.
*/
- static function render() {
+ public static function render() {
if ( empty( self::$store ) ) {
return;
}
diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php
index ce20eac43b3cbd..41223c08158869 100644
--- a/lib/experimental/interactivity-api/directive-processing.php
+++ b/lib/experimental/interactivity-api/directive-processing.php
@@ -65,7 +65,7 @@ function gutenberg_interactivity_mark_inner_blocks( $parsed_block, $source_block
* WP_Directive_Processor.
*/
function gutenberg_interactivity_process_directives( $tags, $prefix, $directives ) {
- $context = new WP_Directive_Context;
+ $context = new WP_Directive_Context();
$tag_stack = array();
while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
diff --git a/lib/experimental/kses.php b/lib/experimental/kses.php
index 6fa8ca641772a2..d447b10fcbd05d 100644
--- a/lib/experimental/kses.php
+++ b/lib/experimental/kses.php
@@ -60,7 +60,6 @@ function gutenberg_override_core_kses_init_filters() {
remove_filter( 'content_filtered_save_pre', 'wp_filter_global_styles_post', 9 );
add_filter( 'content_filtered_save_pre', 'gutenberg_filter_global_styles_post', 9 );
}
-
}
// The 'kses_init_filters' is usually initialized with default priority. Use higher priority to override.
add_action( 'init', 'gutenberg_override_core_kses_init_filters', 20 );
diff --git a/lib/experimental/navigation-theme-opt-in.php b/lib/experimental/navigation-theme-opt-in.php
index efd1a6eec5cb8c..547aa8b88ea7d1 100644
--- a/lib/experimental/navigation-theme-opt-in.php
+++ b/lib/experimental/navigation-theme-opt-in.php
@@ -161,7 +161,7 @@ function gutenberg_remove_block_nav_menu_items( $menu_items ) {
return array_filter(
$menu_items,
- static function( $menu_item ) {
+ static function ( $menu_item ) {
return 'block' !== $menu_item->type;
}
);
diff --git a/packages/block-library/src/comment-template/index.php b/packages/block-library/src/comment-template/index.php
index 5a0eef5685cf1c..915c5880a8f962 100644
--- a/packages/block-library/src/comment-template/index.php
+++ b/packages/block-library/src/comment-template/index.php
@@ -28,7 +28,7 @@ function block_core_comment_template_render_comments( $comments, $block ) {
$content = '';
foreach ( $comments as $comment ) {
$comment_id = $comment->comment_ID;
- $filter_block_context = static function( $context ) use ( $comment_id ) {
+ $filter_block_context = static function ( $context ) use ( $comment_id ) {
$context['commentId'] = $comment_id;
return $context;
};
diff --git a/packages/block-library/src/comments-pagination-next/index.php b/packages/block-library/src/comments-pagination-next/index.php
index 5a8df691ff0924..51d1f75c58d477 100644
--- a/packages/block-library/src/comments-pagination-next/index.php
+++ b/packages/block-library/src/comments-pagination-next/index.php
@@ -26,7 +26,7 @@ function render_block_core_comments_pagination_next( $attributes, $content, $blo
$label = isset( $attributes['label'] ) && ! empty( $attributes['label'] ) ? $attributes['label'] : $default_label;
$pagination_arrow = get_comments_pagination_arrow( $block, 'next' );
- $filter_link_attributes = static function() {
+ $filter_link_attributes = static function () {
return get_block_wrapper_attributes();
};
add_filter( 'next_comments_link_attributes', $filter_link_attributes );
diff --git a/packages/block-library/src/comments-pagination-previous/index.php b/packages/block-library/src/comments-pagination-previous/index.php
index ed3fa653abda9e..f194e8ab517f57 100644
--- a/packages/block-library/src/comments-pagination-previous/index.php
+++ b/packages/block-library/src/comments-pagination-previous/index.php
@@ -22,7 +22,7 @@ function render_block_core_comments_pagination_previous( $attributes, $content,
$label = $pagination_arrow . $label;
}
- $filter_link_attributes = static function() {
+ $filter_link_attributes = static function () {
return get_block_wrapper_attributes();
};
add_filter( 'previous_comments_link_attributes', $filter_link_attributes );
diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php
index 4cbdf1ca61bed0..356ba5032689f7 100644
--- a/packages/block-library/src/latest-posts/index.php
+++ b/packages/block-library/src/latest-posts/index.php
@@ -48,7 +48,7 @@ function render_block_core_latest_posts( $attributes ) {
$block_core_latest_posts_excerpt_length = $attributes['excerptLength'];
add_filter( 'excerpt_length', 'block_core_latest_posts_get_excerpt_length', 20 );
- $filter_latest_posts_excerpt_more = static function( $more ) use ( $attributes ) {
+ $filter_latest_posts_excerpt_more = static function ( $more ) use ( $attributes ) {
$use_excerpt = 'excerpt' === $attributes['displayPostContentRadio'];
/* translators: %1$s is a URL to a post, excerpt truncation character, default … */
return $use_excerpt ? sprintf( __( ' … Read more ' ), esc_url( get_permalink() ) ) : $more;
diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php
index 6d5bb07335f2af..082db8c998a05d 100644
--- a/packages/block-library/src/navigation/index.php
+++ b/packages/block-library/src/navigation/index.php
@@ -105,7 +105,7 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes
$w->set_attribute( 'data-wp-on--click', 'actions.core.navigation.toggleMenuOnClick' );
$w->set_attribute( 'data-wp-bind--aria-expanded', 'selectors.core.navigation.isMenuOpen' );
// The `aria-expanded` attribute for SSR is already added in the submenu block.
- };
+ }
// Add directives to the submenu.
if ( $w->next_tag(
array(
@@ -120,7 +120,7 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes
block_core_navigation_add_directives_to_submenu( $w, $block_attributes );
}
return $w->get_updated_html();
-};
+}
/**
* Build an array with CSS classes and inline styles defining the colors
@@ -263,7 +263,7 @@ function block_core_navigation_render_submenu_icon() {
function block_core_navigation_filter_out_empty_blocks( $parsed_blocks ) {
$filtered = array_filter(
$parsed_blocks,
- static function( $block ) {
+ static function ( $block ) {
return isset( $block['blockName'] );
}
);
@@ -904,7 +904,7 @@ function block_core_navigation_get_classic_menu_fallback() {
// Otherwise return the most recently created classic menu.
usort(
$classic_nav_menus,
- static function( $a, $b ) {
+ static function ( $a, $b ) {
return $b->term_id - $a->term_id;
}
);
diff --git a/packages/block-library/src/post-excerpt/index.php b/packages/block-library/src/post-excerpt/index.php
index 4ed4edab95078b..09d6b6f003d34b 100644
--- a/packages/block-library/src/post-excerpt/index.php
+++ b/packages/block-library/src/post-excerpt/index.php
@@ -31,7 +31,7 @@ function render_block_core_post_excerpt( $attributes, $content, $block ) {
}
$more_text = ! empty( $attributes['moreText'] ) ? '' . wp_kses_post( $attributes['moreText'] ) . ' ' : '';
- $filter_excerpt_more = static function( $more ) use ( $more_text ) {
+ $filter_excerpt_more = static function ( $more ) use ( $more_text ) {
return empty( $more_text ) ? $more : '';
};
/**
@@ -87,7 +87,7 @@ function register_block_core_post_excerpt() {
defined( 'REST_REQUEST' ) && REST_REQUEST ) {
add_filter(
'excerpt_length',
- static function() {
+ static function () {
return 100;
},
PHP_INT_MAX
diff --git a/packages/block-library/src/post-template/index.php b/packages/block-library/src/post-template/index.php
index e616939514a682..2c7502ca3b6653 100644
--- a/packages/block-library/src/post-template/index.php
+++ b/packages/block-library/src/post-template/index.php
@@ -106,7 +106,7 @@ function render_block_core_post_template( $attributes, $content, $block ) {
$post_id = get_the_ID();
$post_type = get_post_type();
- $filter_block_context = static function( $context ) use ( $post_id, $post_type ) {
+ $filter_block_context = static function ( $context ) use ( $post_id, $post_type ) {
$context['postType'] = $post_type;
$context['postId'] = $post_id;
return $context;
diff --git a/packages/block-library/src/query-pagination-next/index.php b/packages/block-library/src/query-pagination-next/index.php
index 83c177c6fb0a9f..6b1861be8a1ef6 100644
--- a/packages/block-library/src/query-pagination-next/index.php
+++ b/packages/block-library/src/query-pagination-next/index.php
@@ -37,7 +37,7 @@ function render_block_core_query_pagination_next( $attributes, $content, $block
// Check if the pagination is for Query that inherits the global context.
if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) {
- $filter_link_attributes = static function() use ( $wrapper_attributes ) {
+ $filter_link_attributes = static function () use ( $wrapper_attributes ) {
return $wrapper_attributes;
};
add_filter( 'next_posts_link_attributes', $filter_link_attributes );
diff --git a/packages/block-library/src/query-pagination-previous/index.php b/packages/block-library/src/query-pagination-previous/index.php
index a580880f0f04c8..be5d62d6b0a918 100644
--- a/packages/block-library/src/query-pagination-previous/index.php
+++ b/packages/block-library/src/query-pagination-previous/index.php
@@ -35,7 +35,7 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl
// Check if the pagination is for Query that inherits the global context
// and handle appropriately.
if ( isset( $block->context['query']['inherit'] ) && $block->context['query']['inherit'] ) {
- $filter_link_attributes = static function() use ( $wrapper_attributes ) {
+ $filter_link_attributes = static function () use ( $wrapper_attributes ) {
return $wrapper_attributes;
};
diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php
index da04a0478e3069..779130d6c21fb3 100644
--- a/packages/block-library/src/search/index.php
+++ b/packages/block-library/src/search/index.php
@@ -182,7 +182,7 @@ function render_block_core_search( $attributes, $content, $block ) {
data-wp-on--keydown="actions.core.search.handleSearchKeydown"
data-wp-on--focusout="actions.core.search.handleSearchFocusout"
';
- };
+ }
return sprintf(
'',
diff --git a/packages/e2e-tests/mu-plugins/enable-templates-ui.php b/packages/e2e-tests/mu-plugins/enable-templates-ui.php
index 0fe1056d4624b0..70b70af7048f85 100644
--- a/packages/e2e-tests/mu-plugins/enable-templates-ui.php
+++ b/packages/e2e-tests/mu-plugins/enable-templates-ui.php
@@ -12,7 +12,7 @@
*/
add_filter(
'register_post_type_args',
- static function( $args, $name ) {
+ static function ( $args, $name ) {
if ( in_array( $name, array( 'wp_template', 'wp_template_part' ), true ) ) {
$args['show_ui'] = wp_is_block_theme();
}
diff --git a/packages/e2e-tests/mu-plugins/nocache-headers.php b/packages/e2e-tests/mu-plugins/nocache-headers.php
index 0bb88030e68f5b..7b66b4eea336ca 100644
--- a/packages/e2e-tests/mu-plugins/nocache-headers.php
+++ b/packages/e2e-tests/mu-plugins/nocache-headers.php
@@ -12,7 +12,7 @@
// for 'networkidle'.
add_filter(
'nocache_headers',
- static function( $headers ) {
+ static function ( $headers ) {
$cache_control_parts = explode( ', ', $headers['Cache-Control'] );
$cache_control_parts = array_diff( $cache_control_parts, array( 'no-store' ) );
$headers['Cache-Control'] = implode( ', ', $cache_control_parts );
diff --git a/packages/e2e-tests/plugins/block-context.php b/packages/e2e-tests/plugins/block-context.php
index d245efce2a6156..f640665bb2f0ed 100644
--- a/packages/e2e-tests/plugins/block-context.php
+++ b/packages/e2e-tests/plugins/block-context.php
@@ -47,7 +47,7 @@ function gutenberg_test_register_context_blocks() {
'postId',
'postType',
),
- 'render_callback' => static function( $attributes, $content, $block ) {
+ 'render_callback' => static function ( $attributes, $content, $block ) {
$ordered_context = array(
$block->context['gutenberg/recordId'],
$block->context['postId'],
diff --git a/packages/e2e-tests/plugins/iframed-block.php b/packages/e2e-tests/plugins/iframed-block.php
index 574a48400ea2fb..329f3e26a479cb 100644
--- a/packages/e2e-tests/plugins/iframed-block.php
+++ b/packages/e2e-tests/plugins/iframed-block.php
@@ -9,14 +9,14 @@
add_action(
'setup_theme',
- static function() {
+ static function () {
add_theme_support( 'block-templates' );
}
);
add_action(
'init',
- static function() {
+ static function () {
wp_register_script(
'iframed-block-jquery-test',
plugin_dir_url( __FILE__ ) . 'iframed-block/jquery.test.js',
diff --git a/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php b/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php
index 3f24a6e25cfcb5..d02bfc6e2d34b7 100644
--- a/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php
+++ b/packages/e2e-tests/plugins/iframed-enqueue-block-assets.php
@@ -9,7 +9,7 @@
add_action(
'enqueue_block_assets',
- static function() {
+ static function () {
wp_enqueue_style(
'iframed-enqueue-block-assets',
plugin_dir_url( __FILE__ ) . 'iframed-enqueue-block-assets/style.css',
diff --git a/packages/e2e-tests/plugins/iframed-enqueue-block-editor-settings.php b/packages/e2e-tests/plugins/iframed-enqueue-block-editor-settings.php
index 2e5eb32c042784..ef6361208848d7 100644
--- a/packages/e2e-tests/plugins/iframed-enqueue-block-editor-settings.php
+++ b/packages/e2e-tests/plugins/iframed-enqueue-block-editor-settings.php
@@ -9,7 +9,7 @@
add_action(
'block_editor_settings_all',
- function( $settings ) {
+ function ( $settings ) {
$settings['styles'][] = array(
'css' => 'p { border: 1px solid red }',
'__unstableType' => 'plugin',
diff --git a/packages/e2e-tests/plugins/iframed-inline-styles.php b/packages/e2e-tests/plugins/iframed-inline-styles.php
index ec44e371af6f08..0f8c69e1073337 100644
--- a/packages/e2e-tests/plugins/iframed-inline-styles.php
+++ b/packages/e2e-tests/plugins/iframed-inline-styles.php
@@ -9,14 +9,14 @@
add_action(
'setup_theme',
- static function() {
+ static function () {
add_theme_support( 'block-templates' );
}
);
add_action(
'init',
- static function() {
+ static function () {
wp_register_script(
'iframed-inline-styles-editor-script',
plugin_dir_url( __FILE__ ) . 'iframed-inline-styles/editor.js',
@@ -40,7 +40,7 @@ static function() {
add_action(
'enqueue_block_editor_assets',
- static function() {
+ static function () {
wp_enqueue_style(
'iframed-inline-styles-compat-style',
plugin_dir_url( __FILE__ ) . 'iframed-inline-styles/compat-style.css',
diff --git a/packages/e2e-tests/plugins/iframed-masonry-block.php b/packages/e2e-tests/plugins/iframed-masonry-block.php
index 3e7948ae2fa127..ebfbdb6b1257ec 100644
--- a/packages/e2e-tests/plugins/iframed-masonry-block.php
+++ b/packages/e2e-tests/plugins/iframed-masonry-block.php
@@ -9,14 +9,14 @@
add_action(
'setup_theme',
- static function() {
+ static function () {
add_theme_support( 'block-templates' );
}
);
add_action(
'init',
- static function() {
+ static function () {
wp_register_script(
'iframed-masonry-block-editor',
plugin_dir_url( __FILE__ ) . 'iframed-masonry-block/editor.js',
diff --git a/packages/e2e-tests/plugins/iframed-multiple-stylesheets.php b/packages/e2e-tests/plugins/iframed-multiple-stylesheets.php
index e70ba31938d14b..6a4505ed065228 100644
--- a/packages/e2e-tests/plugins/iframed-multiple-stylesheets.php
+++ b/packages/e2e-tests/plugins/iframed-multiple-stylesheets.php
@@ -9,14 +9,14 @@
add_action(
'setup_theme',
- static function() {
+ static function () {
add_theme_support( 'block-templates' );
}
);
add_action(
'init',
- static function() {
+ static function () {
wp_register_script(
'iframed-multiple-stylesheets-editor-script',
plugin_dir_url( __FILE__ ) . 'iframed-multiple-stylesheets/editor.js',
diff --git a/packages/e2e-tests/plugins/interactive-blocks.php b/packages/e2e-tests/plugins/interactive-blocks.php
index 8a44e5a0efd4ac..a6bd468493840d 100644
--- a/packages/e2e-tests/plugins/interactive-blocks.php
+++ b/packages/e2e-tests/plugins/interactive-blocks.php
@@ -9,7 +9,7 @@
add_action(
'init',
- function() {
+ function () {
// Register all blocks found in the `interactive-blocks` folder.
if ( file_exists( __DIR__ . '/interactive-blocks/' ) ) {
$block_json_files = glob( __DIR__ . '/interactive-blocks/**/block.json' );
@@ -30,8 +30,8 @@ function() {
);
register_block_type_from_metadata( $block_folder );
- };
- };
+ }
+ }
// Temporary fix to disable SSR of directives during E2E testing. This
// is required at this moment, as SSR for directives is not stabilized
@@ -43,6 +43,5 @@ function() {
'gutenberg_interactivity_process_directives_in_root_blocks'
);
}
-
}
);
diff --git a/packages/e2e-tests/plugins/marquee-function-widget.php b/packages/e2e-tests/plugins/marquee-function-widget.php
index 85881bab14c495..389641489af841 100644
--- a/packages/e2e-tests/plugins/marquee-function-widget.php
+++ b/packages/e2e-tests/plugins/marquee-function-widget.php
@@ -14,7 +14,7 @@ function marquee_greeting_init() {
wp_register_sidebar_widget(
'marquee_greeting',
'Marquee Greeting',
- static function() {
+ static function () {
$greeting = get_option( 'marquee_greeting', 'Hello!' );
printf( '%s ', esc_html( $greeting ) );
}
@@ -23,7 +23,7 @@ static function() {
wp_register_widget_control(
'marquee_greeting',
'Marquee Greeting',
- static function() {
+ static function () {
if ( isset( $_POST['marquee-greeting'] ) ) {
update_option(
'marquee_greeting',
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index e8c672f130f7c5..d383de10726d21 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -10,15 +10,20 @@
-
-
+
+
+
+
+
+
+
+
-
@@ -48,6 +53,7 @@
./packages/block-serialization-spec-parser/parser.php
node_modules/*
+ build/*
./vendor/*
@@ -64,6 +70,7 @@
phpunit/*
+ test/gutenberg-test-themes/*
**/*.min.asset.php
@@ -90,22 +97,16 @@
phpunit/*
-
-
- ./packages/block-serialization-default-parser/parser.php
-
-
- ./packages/block-serialization-default-parser/parser.php
-
-
- ./packages/block-serialization-default-parser/parser.php
-
- ./packages/block-serialization-default-parser/parser.php
/phpunit/*
+
+
+ ./test/gutenberg-test-themes/*
+
+
/phpunit/*
diff --git a/phpunit/blocks/render-block-navigation-submenu-test.php b/phpunit/blocks/render-block-navigation-submenu-test.php
index 86ca34f5b997f8..4b60cab4514c94 100644
--- a/phpunit/blocks/render-block-navigation-submenu-test.php
+++ b/phpunit/blocks/render-block-navigation-submenu-test.php
@@ -208,6 +208,5 @@ public function test_should_not_apply_custom_colors_if_missing_from_context() {
'has-text-color has-background',
'Submenu block should not apply "has-*" color classes if missing from context'
);
-
}
}
diff --git a/phpunit/blocks/render-block-navigation-test.php b/phpunit/blocks/render-block-navigation-test.php
index ca4bbb6c342789..b8266d510ad6c6 100644
--- a/phpunit/blocks/render-block-navigation-test.php
+++ b/phpunit/blocks/render-block-navigation-test.php
@@ -85,5 +85,4 @@ public function test_gutenberg_block_core_navigation_block_contains_core_navigat
$inner_blocks = new WP_Block_List( $parsed_blocks );
$this->assertFalse( gutenberg_block_core_navigation_block_contains_core_navigation( $inner_blocks ) );
}
-
}
diff --git a/phpunit/blocks/render-comment-template-test.php b/phpunit/blocks/render-comment-template-test.php
index c297d1729d0dc5..64700424473aad 100644
--- a/phpunit/blocks/render-comment-template-test.php
+++ b/phpunit/blocks/render-comment-template-test.php
@@ -91,7 +91,7 @@ public function test_rendering_comment_template_sets_comment_id_context() {
'Comment Author Name block rendered markup is empty.'
);
- $render_block_callback = static function( $block_content, $block ) use ( $parsed_comment_author_name_block ) {
+ $render_block_callback = static function ( $block_content, $block ) use ( $parsed_comment_author_name_block ) {
// Insert a Comment Author Name block (which requires `commentId`
// block context to work) after the Comment Content block.
if ( 'core/comment-content' !== $block['blockName'] ) {
@@ -126,7 +126,7 @@ public function test_inner_block_inserted_by_render_block_data_is_retained() {
$render_block_callback = new MockAction();
add_filter( 'render_block', array( $render_block_callback, 'filter' ), 10, 3 );
- $render_block_data_callback = static function( $parsed_block ) {
+ $render_block_data_callback = static function ( $parsed_block ) {
// Add a Social Links block to a Comment Template block's inner blocks.
if ( 'core/comment-template' === $parsed_block['blockName'] ) {
$inserted_block_markup = << static function() use ( &$in_the_loop_logs ) {
+ 'render_callback' => static function () use ( &$in_the_loop_logs ) {
$in_the_loop_logs[] = in_the_loop();
return '';
},
diff --git a/phpunit/class-block-context-test.php b/phpunit/class-block-context-test.php
index 6c06cfd099bd4f..49bbb560e8022d 100644
--- a/phpunit/class-block-context-test.php
+++ b/phpunit/class-block-context-test.php
@@ -104,7 +104,7 @@ public function test_provides_block_context() {
'gutenberg/contextWithAssigned',
'gutenberg/contextWithoutDefault',
),
- 'render_callback' => static function( $attributes, $content, $block ) use ( &$provided_context ) {
+ 'render_callback' => static function ( $attributes, $content, $block ) use ( &$provided_context ) {
$provided_context[] = $block->context;
return '';
@@ -142,7 +142,7 @@ public function test_provides_default_context() {
'gutenberg/test-context-consumer',
array(
'uses_context' => array( 'postId', 'postType' ),
- 'render_callback' => static function( $attributes, $content, $block ) use ( &$provided_context ) {
+ 'render_callback' => static function ( $attributes, $content, $block ) use ( &$provided_context ) {
$provided_context[] = $block->context;
return '';
@@ -173,7 +173,7 @@ public function test_default_context_is_filterable() {
'gutenberg/test-context-consumer',
array(
'uses_context' => array( 'example' ),
- 'render_callback' => static function( $attributes, $content, $block ) use ( &$provided_context ) {
+ 'render_callback' => static function ( $attributes, $content, $block ) use ( &$provided_context ) {
$provided_context[] = $block->context;
return '';
@@ -181,7 +181,7 @@ public function test_default_context_is_filterable() {
)
);
- $filter_block_context = static function( $context ) {
+ $filter_block_context = static function ( $context ) {
$context['example'] = 'ok';
return $context;
};
diff --git a/phpunit/class-block-library-navigation-link-test.php b/phpunit/class-block-library-navigation-link-test.php
index b0a5b989a9ae83..7223a7d45abda9 100644
--- a/phpunit/class-block-library-navigation-link-test.php
+++ b/phpunit/class-block-library-navigation-link-test.php
@@ -246,7 +246,7 @@ public function test_returns_link_for_decoded_link() {
$urls_after_render[ $idx ]
) !== false
);
- };
+ }
}
public function test_returns_empty_when_custom_post_type_draft() {
diff --git a/phpunit/class-gutenberg-navigation-fallback-gutenberg-test.php b/phpunit/class-gutenberg-navigation-fallback-gutenberg-test.php
index edc48f1c71761b..6ae05e0a5ffb7f 100644
--- a/phpunit/class-gutenberg-navigation-fallback-gutenberg-test.php
+++ b/phpunit/class-gutenberg-navigation-fallback-gutenberg-test.php
@@ -182,7 +182,6 @@ public function test_should_return_fallback_navigation_from_existing_classic_men
// Check that only a single Navigation fallback was created.
$navs_in_db = $this->get_navigations_in_database();
$this->assertCount( 1, $navs_in_db, 'A single Navigation menu should be present in the database.' );
-
}
/**
@@ -335,7 +334,6 @@ public function test_should_not_create_fallback_from_classic_menu_if_a_navigatio
$navs_in_db = $this->get_navigations_in_database();
$this->assertCount( 1, $navs_in_db, 'Only the existing Navigation menus should be present in the database.' );
-
}
private function get_navigations_in_database() {
@@ -351,5 +349,4 @@ private function get_navigations_in_database() {
return $navs_in_db->posts ? $navs_in_db->posts : array();
}
-
}
diff --git a/phpunit/class-gutenberg-rest-navigation-fallback-controller-test.php b/phpunit/class-gutenberg-rest-navigation-fallback-controller-test.php
index d6df0c3c504ee3..0132bc53015e66 100644
--- a/phpunit/class-gutenberg-rest-navigation-fallback-controller-test.php
+++ b/phpunit/class-gutenberg-rest-navigation-fallback-controller-test.php
@@ -87,7 +87,6 @@ public function test_get_item() {
$navs_in_db = $this->get_navigations_in_database();
$this->assertCount( 1, $navs_in_db, 'Only a single Navigation menu should be present in the database.' );
-
}
/**
diff --git a/phpunit/class-phpunit-environment-setup-test.php b/phpunit/class-phpunit-environment-setup-test.php
index 6dd03a5a1f5afb..a704144ea17f0f 100644
--- a/phpunit/class-phpunit-environment-setup-test.php
+++ b/phpunit/class-phpunit-environment-setup-test.php
@@ -17,5 +17,4 @@ public function test_is_multisite_if_defined() {
$this->assertEquals( $is_multisite_in_env, is_multisite(), 'Despite WP_MULTISITE being set in the environment, the tests were not run in multisite mode. There is likely an issue with the phpunit setup.' );
}
-
}
diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php
index 1588aa4d603265..eda8caede7bfec 100644
--- a/phpunit/class-wp-theme-json-resolver-test.php
+++ b/phpunit/class-wp-theme-json-resolver-test.php
@@ -229,13 +229,13 @@ public function test_add_theme_supports_are_loaded_for_themes_without_theme_json
/**
* Recursively applies ksort to an array.
*/
- private static function recursive_ksort( &$array ) {
- foreach ( $array as &$value ) {
+ private static function recursive_ksort( &$input_array ) {
+ foreach ( $input_array as &$value ) {
if ( is_array( $value ) ) {
self::recursive_ksort( $value );
}
}
- ksort( $array );
+ ksort( $input_array );
}
public function test_merges_child_theme_json_into_parent_theme_json() {
@@ -346,7 +346,7 @@ public function test_get_user_data_from_wp_global_styles_does_not_use_uncached_q
$this->assertNotEmpty( $user_cpt, 'User CPT is expected not to be empty.' );
$query_count = count( $this->queries );
- for ( $i = 0; $i < 3; $i ++ ) {
+ for ( $i = 0; $i < 3; $i++ ) {
$new_user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( $theme );
WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data();
$this->assertSameSets( $user_cpt, $new_user_cpt, "User CPTs do not match on run {$i}." );
@@ -438,7 +438,7 @@ public function test_get_merged_data_returns_origin( $origin, $core_palette, $co
$this->assertSame( $core_palette, isset( $settings['color']['palette']['default'] ), $core_palette_text );
$styles = array_filter(
$styles,
- static function( $element ) {
+ static function ( $element ) {
return isset( $element['name'] ) && 'my/block-with-styles' === $element['name'];
}
);
@@ -568,5 +568,4 @@ public function test_get_style_variations_returns_all_variations() {
$actual_settings
);
}
-
}
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index 11ec35e0925bfe..619be4ce806c8f 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -1604,7 +1604,6 @@ public function test_sanitize_with_invalid_style_variation( $theme_json_variatio
$sanitized_theme_json = $theme_json->get_raw_data();
$this->assertIsArray( $sanitized_theme_json, 'Sanitized theme.json is not an array data type' );
$this->assertArrayNotHasKey( 'styles', $sanitized_theme_json, 'Sanitized theme.json should not have a "styles" key' );
-
}
/**
@@ -1892,7 +1891,6 @@ public function test_update_separator_declarations() {
$expected = $base_styles . '.wp-block-separator{background-color: blue;border-color: pink;}';
$stylesheet = $theme_json->get_stylesheet( array( 'styles' ) );
$this->assertEquals( $expected, $stylesheet );
-
}
public function test_get_custom_css_handles_global_custom_css() {
@@ -2098,7 +2096,6 @@ public function test_internal_syntax_is_converted_to_css_variables() {
$this->assertEquals( 'var(--wp--preset--font-size--s)', $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Style variations: Assert the originally correct values are still correct.' );
$this->assertEquals( 'var(--wp--preset--color--s)', $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Style variations: Assert the internal variables are convert to CSS custom variables.' );
-
}
public function test_resolve_variables() {
@@ -2253,5 +2250,4 @@ public function test_resolve_variables() {
$this->assertEquals( $small_font, $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Block variations: font-size' );
$this->assertEquals( $secondary_color, $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Block variations: color' );
}
-
}
diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php
index 4da0a4a85ac3c0..44838b4ce68d3e 100644
--- a/phpunit/experimental/interactivity-api/directive-processing-test.php
+++ b/phpunit/experimental/interactivity-api/directive-processing-test.php
@@ -4,18 +4,21 @@
*
* @package Gutenberg
* @subpackage Interactivity API
+ *
+ * @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
*/
+
class Helper_Class {
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
- function process_foo_test( $tags, $context ) {
+ public function process_foo_test( $tags, $context ) {
}
- function increment( $store ) {
+ public function increment( $store ) {
return $store['state']['count'] + $store['context']['count'];
}
- static function static_increment( $store ) {
+ public static function static_increment( $store ) {
return $store['state']['count'] + $store['context']['count'];
}
}
@@ -40,7 +43,7 @@ public function test_correctly_call_attribute_directive_processor_on_closing_tag
->method( 'process_foo_test' )
->with(
$this->callback(
- function( $p ) {
+ function ( $p ) {
return 'DIV' === $p->get_tag() && (
// Either this is a closing tag...
$p->is_tag_closer() ||
@@ -129,7 +132,7 @@ public function test_evaluate_function_should_return_null_for_unresolved_paths()
public function test_evaluate_function_should_execute_anonymous_functions() {
$context = new WP_Directive_Context( array( 'count' => 2 ) );
- $helper = new Helper_Class;
+ $helper = new Helper_Class();
wp_store(
array(
@@ -137,7 +140,7 @@ public function test_evaluate_function_should_execute_anonymous_functions() {
'count' => 3,
),
'selectors' => array(
- 'anonymous_function' => function( $store ) {
+ 'anonymous_function' => function ( $store ) {
return $store['state']['count'] + $store['context']['count'];
},
// Other types of callables should not be executed.
diff --git a/phpunit/experimental/interactivity-api/directives/wp-context-test.php b/phpunit/experimental/interactivity-api/directives/wp-context-test.php
index 90ffb7dd9bf296..0f36f38ba837db 100644
--- a/phpunit/experimental/interactivity-api/directives/wp-context-test.php
+++ b/phpunit/experimental/interactivity-api/directives/wp-context-test.php
@@ -73,5 +73,4 @@ public function test_directive_doesnt_throw_on_malformed_context_objects() {
$context->get_context()
);
}
-
}
diff --git a/phpunit/l10n-test.php b/phpunit/l10n-test.php
index 4f71e9e89e2279..b191bbd1babc38 100644
--- a/phpunit/l10n-test.php
+++ b/phpunit/l10n-test.php
@@ -13,19 +13,19 @@ class Tests_L10n_wpWordcount extends WP_UnitTestCase {
*
* @dataProvider data_get_string_variations
*
- * @param string $string String to count words.
+ * @param string $input_string String to count words.
* @param int $words Expected value if the count type is based on word.
* @param int $characters_excluding_spaces Expected value if the count type is based on single character excluding spaces.
* @param int $characters_including_spaces Expected value if the count type is based on single character including spaces.
*/
- public function test_word_count( $string, $words, $characters_excluding_spaces, $characters_including_spaces ) {
+ public function test_word_count( $input_string, $words, $characters_excluding_spaces, $characters_including_spaces ) {
$settings = array(
'shortcodes' => array( 'shortcode' ),
);
- $this->assertEquals( wp_word_count( $string, 'words', $settings ), $words );
- $this->assertEquals( wp_word_count( $string, 'characters_excluding_spaces', $settings ), $characters_excluding_spaces );
- $this->assertEquals( wp_word_count( $string, 'characters_including_spaces', $settings ), $characters_including_spaces );
+ $this->assertEquals( wp_word_count( $input_string, 'words', $settings ), $words );
+ $this->assertEquals( wp_word_count( $input_string, 'characters_excluding_spaces', $settings ), $characters_excluding_spaces );
+ $this->assertEquals( wp_word_count( $input_string, 'characters_including_spaces', $settings ), $characters_including_spaces );
}
/**
diff --git a/phpunit/tests/fonts-api/base.php b/phpunit/tests/fonts-api/base.php
index 11ae3b624ea8ca..5d03e2733964af 100644
--- a/phpunit/tests/fonts-api/base.php
+++ b/phpunit/tests/fonts-api/base.php
@@ -161,12 +161,12 @@ protected function set_up_mock( $method ) {
return $mock;
}
- protected function setup_object_mock( $method, $class ) {
+ protected function setup_object_mock( $method, $class_name ) {
if ( is_string( $method ) ) {
$method = array( $method );
}
- return $this->getMockBuilder( $class )->setMethods( $method )->getMock();
+ return $this->getMockBuilder( $class_name )->setMethods( $method )->getMock();
}
protected function get_registered_handles() {
@@ -193,15 +193,15 @@ protected function get_queued_before_register( $wp_fonts = null ) {
return $this->get_property_value( 'queued_before_register', WP_Dependencies::class, $wp_fonts );
}
- protected function get_reflection_property( $property_name, $class = 'WP_Fonts' ) {
- $property = new ReflectionProperty( $class, $property_name );
+ protected function get_reflection_property( $property_name, $class_name = 'WP_Fonts' ) {
+ $property = new ReflectionProperty( $class_name, $property_name );
$property->setAccessible( true );
return $property;
}
- protected function get_property_value( $property_name, $class, $wp_fonts = null ) {
- $property = $this->get_reflection_property( $property_name, $class );
+ protected function get_property_value( $property_name, $class_name, $wp_fonts = null ) {
+ $property = $this->get_reflection_property( $property_name, $class_name );
if ( ! $wp_fonts ) {
$wp_fonts = wp_fonts();
@@ -210,19 +210,19 @@ protected function get_property_value( $property_name, $class, $wp_fonts = null
return $property->getValue( $wp_fonts );
}
- protected function setup_property( $class, $property_name ) {
- $key = $this->get_property_key( $class, $property_name );
+ protected function setup_property( $class_name, $property_name ) {
+ $key = $this->get_property_key( $class_name, $property_name );
if ( ! isset( $this->property[ $key ] ) ) {
- $this->property[ $key ] = new ReflectionProperty( $class, 'providers' );
+ $this->property[ $key ] = new ReflectionProperty( $class_name, 'providers' );
$this->property[ $key ]->setAccessible( true );
}
return $this->property[ $key ];
}
- protected function get_property_key( $class, $property_name ) {
- return $class . '::$' . $property_name;
+ protected function get_property_key( $class_name, $property_name ) {
+ return $class_name . '::$' . $property_name;
}
/**
diff --git a/phpunit/tests/fonts-api/wpFonts/add.php b/phpunit/tests/fonts-api/wpFonts/add.php
index f9449b87c53b53..85d3d98c0f3556 100644
--- a/phpunit/tests/fonts-api/wpFonts/add.php
+++ b/phpunit/tests/fonts-api/wpFonts/add.php
@@ -25,7 +25,6 @@ public function test_add( $handle ) {
$this->assertTrue( $wp_fonts->add( $handle, false ), 'Registering a handle should return true' );
$this->assertCount( 1, $wp_fonts->registered );
$this->assertArrayHasKey( $handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' );
-
}
/**
diff --git a/phpunit/tests/fonts-api/wpFonts/addFontFamily.php b/phpunit/tests/fonts-api/wpFonts/addFontFamily.php
index f227c2c4025a40..69b6bccf5ae352 100644
--- a/phpunit/tests/fonts-api/wpFonts/addFontFamily.php
+++ b/phpunit/tests/fonts-api/wpFonts/addFontFamily.php
@@ -27,7 +27,6 @@ public function test_should_register( $font_family, $expected ) {
$this->assertSame( $expected, $font_family_handle, 'Registering a font-family should return its handle' );
$this->assertCount( 1, $wp_fonts->registered );
$this->assertArrayHasKey( $font_family_handle, $wp_fonts->registered, 'Font family handle should be in the registry after registration' );
-
}
/**
diff --git a/phpunit/tests/fonts-api/wpFonts/doItem.php b/phpunit/tests/fonts-api/wpFonts/doItem.php
index af46f376b3bc5c..a92bd244d1f2ae 100644
--- a/phpunit/tests/fonts-api/wpFonts/doItem.php
+++ b/phpunit/tests/fonts-api/wpFonts/doItem.php
@@ -18,7 +18,7 @@ class Tests_Fonts_WpFonts_DoItem extends WP_Fonts_TestCase {
public function set_up() {
parent::set_up();
- $this->wp_fonts = new WP_Fonts;
+ $this->wp_fonts = new WP_Fonts();
}
public function test_should_return_false_when_provider_not_registered() {
diff --git a/phpunit/tests/fonts-api/wpFonts/doItems.php b/phpunit/tests/fonts-api/wpFonts/doItems.php
index 23307366101cb0..8a29e7463406a8 100644
--- a/phpunit/tests/fonts-api/wpFonts/doItems.php
+++ b/phpunit/tests/fonts-api/wpFonts/doItems.php
@@ -19,7 +19,7 @@ class Tests_Fonts_WpFonts_DoItems extends WP_Fonts_TestCase {
public function set_up() {
parent::set_up();
- $this->wp_fonts = new WP_Fonts;
+ $this->wp_fonts = new WP_Fonts();
}
public function test_should_not_process_when_no_providers_registered() {
diff --git a/phpunit/tests/fonts-api/wpFonts/registerProvider.php b/phpunit/tests/fonts-api/wpFonts/registerProvider.php
index 09d3d7f3891720..c67976e0a68655 100644
--- a/phpunit/tests/fonts-api/wpFonts/registerProvider.php
+++ b/phpunit/tests/fonts-api/wpFonts/registerProvider.php
@@ -19,12 +19,12 @@ class Tests_Fonts_WpFonts_RegisterProvider extends WP_Fonts_TestCase {
* @dataProvider data_register_providers
*
* @param string $provider_id Provider ID.
- * @param string $class Provider class name.
+ * @param string $class_name Provider class name.
* @param array $expected Expected providers queue.
*/
- public function test_should_register_provider( $provider_id, $class, $expected ) {
+ public function test_should_register_provider( $provider_id, $class_name, $expected ) {
$wp_fonts = new WP_Fonts();
- $this->assertTrue( $wp_fonts->register_provider( $provider_id, $class ), 'WP_Fonts::register_provider() should return true' );
+ $this->assertTrue( $wp_fonts->register_provider( $provider_id, $class_name ), 'WP_Fonts::register_provider() should return true' );
$this->assertSame( $expected, $wp_fonts->get_providers(), 'Provider "' . $provider_id . '" should be registered in providers queue' );
}
@@ -83,12 +83,12 @@ public function test_should_register_multiple_providers() {
* @dataProvider data_invalid_providers
*
* @param string $provider_id Provider ID.
- * @param string $class Provider class name.
+ * @param string $class_name Provider class name.
*/
- public function test_should_not_register( $provider_id, $class ) {
+ public function test_should_not_register( $provider_id, $class_name ) {
$wp_fonts = new WP_Fonts();
- $this->assertFalse( $wp_fonts->register_provider( $provider_id, $class ), 'WP_Fonts::register_provider() should return false' );
+ $this->assertFalse( $wp_fonts->register_provider( $provider_id, $class_name ), 'WP_Fonts::register_provider() should return false' );
$this->assertArrayNotHasKey( $provider_id, $wp_fonts->get_providers(), 'Both local and mock providers should be registered' );
}
diff --git a/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php b/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php
index c812b98d3d156d..0186dbf34f3fdc 100644
--- a/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php
+++ b/phpunit/tests/fonts-api/wpFontsResolver/registerFontsFromThemeJson.php
@@ -261,7 +261,7 @@ public function test_should_skip_when_font_face_not_defined() {
* @return WP_Theme_JSON_Data_Gutenberg| WP_Theme_JSON_Data Modified instance.
* @throws ReflectionException
*/
- $remove_expected_font_family = static function( $theme_json_data ) use ( $expected_font_family ) {
+ $remove_expected_font_family = static function ( $theme_json_data ) use ( $expected_font_family ) {
// Need to get the underlying data array which is in WP_Theme_JSON_Gutenberg | WP_Theme_JSON object.
$property = new ReflectionProperty( $theme_json_data, 'theme_json' );
$property->setAccessible( true );
diff --git a/phpunit/tests/fonts-api/wpRegisterFontProvider.php b/phpunit/tests/fonts-api/wpRegisterFontProvider.php
index 9288ee3850672d..907355bdc91c82 100644
--- a/phpunit/tests/fonts-api/wpRegisterFontProvider.php
+++ b/phpunit/tests/fonts-api/wpRegisterFontProvider.php
@@ -19,19 +19,19 @@ class Tests_Fonts_WpRegisterFontProvider extends WP_Fonts_TestCase {
* @dataProvider data_register_providers
*
* @param string $provider_id Provider ID.
- * @param string $class Provider class name.
+ * @param string $class_name Provider class name.
*/
- public function test_should_register_provider( $provider_id, $class ) {
+ public function test_should_register_provider( $provider_id, $class_name ) {
$mock = $this->set_up_mock( 'register_provider' );
$mock->expects( $this->once() )
->method( 'register_provider' )
->with(
$this->identicalTo( $provider_id ),
- $this->identicalTo( $class )
+ $this->identicalTo( $class_name )
)
->will( $this->returnValue( true ) );
- $this->assertTrue( wp_register_font_provider( $provider_id, $class ), 'wp_register_font_provider() should return true' );
+ $this->assertTrue( wp_register_font_provider( $provider_id, $class_name ), 'wp_register_font_provider() should return true' );
}
/**
@@ -56,20 +56,19 @@ public function data_register_providers() {
* @dataProvider data_invalid_providers
*
* @param string $provider_id Provider ID.
- * @param string $class Provider class name.
+ * @param string $class_name Provider class name.
*/
- public function test_should_not_register( $provider_id, $class ) {
+ public function test_should_not_register( $provider_id, $class_name ) {
$mock = $this->set_up_mock( 'register_provider' );
$mock->expects( $this->once() )
->method( 'register_provider' )
->with(
$this->identicalTo( $provider_id ),
- $this->identicalTo( $class )
+ $this->identicalTo( $class_name )
)
->will( $this->returnValue( false ) );
- $this->assertFalse( wp_register_font_provider( $provider_id, $class ), 'wp_register_font_provider() should return false' );
-
+ $this->assertFalse( wp_register_font_provider( $provider_id, $class_name ), 'wp_register_font_provider() should return false' );
}
/**
diff --git a/phpunit/tests/fonts/font-face/base.php b/phpunit/tests/fonts/font-face/base.php
index 4d8d487cb47c97..4d6c9cc5cc0325 100644
--- a/phpunit/tests/fonts/font-face/base.php
+++ b/phpunit/tests/fonts/font-face/base.php
@@ -140,15 +140,15 @@ public function filter_set_theme_root() {
return self::$theme_root;
}
- protected function get_reflection_property( $property_name, $class = 'WP_Fonts' ) {
- $property = new ReflectionProperty( $class, $property_name );
+ protected function get_reflection_property( $property_name, $class_name = 'WP_Fonts' ) {
+ $property = new ReflectionProperty( $class_name, $property_name );
$property->setAccessible( true );
return $property;
}
- protected function get_property_value( $property_name, $class, $wp_fonts = null ) {
- $property = $this->get_reflection_property( $property_name, $class );
+ protected function get_property_value( $property_name, $class_name, $wp_fonts = null ) {
+ $property = $this->get_reflection_property( $property_name, $class_name );
if ( ! $wp_fonts ) {
$wp_fonts = wp_fonts();
@@ -157,19 +157,19 @@ protected function get_property_value( $property_name, $class, $wp_fonts = null
return $property->getValue( $wp_fonts );
}
- protected function setup_property( $class, $property_name ) {
- $key = $this->get_property_key( $class, $property_name );
+ protected function setup_property( $class_name, $property_name ) {
+ $key = $this->get_property_key( $class_name, $property_name );
if ( ! isset( $this->property[ $key ] ) ) {
- $this->property[ $key ] = new ReflectionProperty( $class, 'providers' );
+ $this->property[ $key ] = new ReflectionProperty( $class_name, 'providers' );
$this->property[ $key ]->setAccessible( true );
}
return $this->property[ $key ];
}
- protected function get_property_key( $class, $property_name ) {
- return $class . '::$' . $property_name;
+ protected function get_property_key( $class_name, $property_name ) {
+ return $class_name . '::$' . $property_name;
}
/**
diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php
index 1d4573cbe060c4..b3d8d1f10a1efc 100644
--- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php
+++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php
@@ -32,5 +32,4 @@ public function test_should_get_no_font_collection_if_the_id_is_not_registered()
$font_collection = WP_Font_Library::get_font_collection( 'not-registered-font-collection' );
$this->assertWPError( $font_collection );
}
-
}
diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollection.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollection.php
index 2b605302e37ffc..ca661a1aac6268 100644
--- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollection.php
+++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollection.php
@@ -45,4 +45,3 @@ public function test_get_non_existing_collection_should_return_404() {
$this->assertSame( 404, $response->get_status() );
}
}
-
diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollections.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollections.php
index bf527093e4df1c..52678aa4cb94ae 100644
--- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollections.php
+++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/getFontCollections.php
@@ -43,4 +43,3 @@ public function test_get_font_collections() {
$this->assertArrayHasKey( 'name', $data[0], 'The response data does not have the key with the collection name.' );
}
}
-
diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php
index 8b8ec28406ac1d..1813f535c109f5 100644
--- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php
+++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php
@@ -50,7 +50,6 @@ public function test_install_fonts( $font_families, $files, $expected_response )
// Compares if the rest of the data is the same.
$this->assertEquals( $expected_font, $installed_font, 'The endpoint answer is not as expected.' );
}
-
}
/**
@@ -416,4 +415,3 @@ public function data_install_with_improper_inputs() {
);
}
}
-
diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/registerRoutes.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/registerRoutes.php
index ee957300090b00..2ac7b93c3a4141 100644
--- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/registerRoutes.php
+++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/registerRoutes.php
@@ -26,4 +26,3 @@ public function test_register_routes() {
$this->assertArrayHasKey( 'GET', $routes['/wp/v2/fonts/collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection intialized.' );
}
}
-
diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php
index 9edcec970146db..3082bfc87f62ef 100644
--- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php
+++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php
@@ -72,7 +72,6 @@ public function test_uninstall() {
$response = rest_get_server()->dispatch( $uninstall_request );
echo ( print_r( $response->get_data(), true ) );
$this->assertSame( 200, $response->get_status(), 'The response status is not 200.' );
-
}
@@ -96,5 +95,3 @@ public function test_uninstall_non_existing_fonts() {
$this->assertSame( 500, $response->get_status(), 'The response status is not 500.' );
}
}
-
-
diff --git a/test/emptytheme/functions.php b/test/emptytheme/functions.php
index 137cbef8de59d4..79ae16f9694a1f 100644
--- a/test/emptytheme/functions.php
+++ b/test/emptytheme/functions.php
@@ -20,12 +20,13 @@ function emptytheme_support() {
add_action( 'after_setup_theme', 'emptytheme_support' );
endif;
-/**
- * Enqueue scripts and styles.
- */
-function emptytheme_scripts() {
- // Enqueue theme stylesheet.
- wp_enqueue_style( 'emptytheme-style', get_template_directory_uri() . '/style.css', array(), wp_get_theme()->get( 'Version' ) );
-}
-
-add_action( 'wp_enqueue_scripts', 'emptytheme_scripts' );
+if ( ! function_exists( 'emptytheme_scripts' ) ) :
+ /**
+ * Enqueue scripts and styles.
+ */
+ function emptytheme_scripts() {
+ // Enqueue theme stylesheet.
+ wp_enqueue_style( 'emptytheme-style', get_template_directory_uri() . '/style.css', array(), wp_get_theme()->get( 'Version' ) );
+ }
+ add_action( 'wp_enqueue_scripts', 'emptytheme_scripts' );
+endif;
diff --git a/test/gutenberg-test-themes/emptyhybrid/functions.php b/test/gutenberg-test-themes/emptyhybrid/functions.php
index 350fe964691434..9d7b70f052d6a2 100644
--- a/test/gutenberg-test-themes/emptyhybrid/functions.php
+++ b/test/gutenberg-test-themes/emptyhybrid/functions.php
@@ -23,11 +23,13 @@ function emptyhybrid_support() {
add_action( 'after_setup_theme', 'emptyhybrid_support' );
endif;
-/**
- * Enqueue scripts and styles.
- */
-function emptyhybrid_scripts() {
- // Enqueue theme stylesheet.
- wp_enqueue_style( 'emptyhybrid-style', get_template_directory_uri() . '/style.css', array(), wp_get_theme()->get( 'Version' ) );
-}
-add_action( 'wp_enqueue_scripts', 'emptyhybrid_scripts' );
+if ( ! function_exists( 'emptyhybrid_scripts' ) ) :
+ /**
+ * Enqueue scripts and styles.
+ */
+ function emptyhybrid_scripts() {
+ // Enqueue theme stylesheet.
+ wp_enqueue_style( 'emptyhybrid-style', get_template_directory_uri() . '/style.css', array(), wp_get_theme()->get( 'Version' ) );
+ }
+ add_action( 'wp_enqueue_scripts', 'emptyhybrid_scripts' );
+endif;