From 3315605a74345acba2dd553c0c20c5ce2d515d97 Mon Sep 17 00:00:00 2001
From: Mikey Binns
Date: Sat, 6 Apr 2024 01:14:02 +0100
Subject: [PATCH 001/600] convert shortcode package to typescript
---
packages/shortcode/package.json | 1 +
packages/shortcode/src/{index.js => index.ts} | 178 ++++++++++++------
.../shortcode/src/test/{index.js => index.ts} | 80 ++++++--
packages/shortcode/tsconfig.json | 9 +
tsconfig.json | 1 +
5 files changed, 202 insertions(+), 67 deletions(-)
rename packages/shortcode/src/{index.js => index.ts} (73%)
rename packages/shortcode/src/test/{index.js => index.ts} (82%)
create mode 100644 packages/shortcode/tsconfig.json
diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json
index a3c3ce78a4edf..6af33b68c6639 100644
--- a/packages/shortcode/package.json
+++ b/packages/shortcode/package.json
@@ -24,6 +24,7 @@
"main": "build/index.js",
"module": "build-module/index.js",
"react-native": "src/index",
+ "types": "build-types",
"dependencies": {
"@babel/runtime": "^7.16.0",
"memize": "^2.0.1"
diff --git a/packages/shortcode/src/index.js b/packages/shortcode/src/index.ts
similarity index 73%
rename from packages/shortcode/src/index.js
rename to packages/shortcode/src/index.ts
index 04e69c272378b..8a43c32140776 100644
--- a/packages/shortcode/src/index.js
+++ b/packages/shortcode/src/index.ts
@@ -11,6 +11,16 @@ import memize from 'memize';
* @property {Object} named Object with named attributes.
* @property {Array} numeric Array with numeric attributes.
*/
+type WPShortcodeAttrs = {
+ /**
+ * Object with named attributes.
+ */
+ named: Record< string, string >;
+ /**
+ * Array with numeric attributes.
+ */
+ numeric: string[];
+};
/**
* Shortcode object.
@@ -23,6 +33,24 @@ import memize from 'memize';
* @property {string} type Shortcode type: `self-closing`,
* `closed`, or `single`.
*/
+type WPShortcodeOptions = {
+ /**
+ * Shortcode tag.
+ */
+ tag: string;
+ /**
+ * Shortcode attributes.
+ */
+ attrs: string | WPShortcodeAttrs | WPShortcodeAttrs[ 'named' ];
+ /**
+ * Shortcode content.
+ */
+ content: string;
+ /**
+ * Shortcode type.
+ */
+ type: 'single' | 'self-closing' | 'closed';
+};
/**
* @typedef {Object} WPShortcodeMatch
@@ -31,6 +59,20 @@ import memize from 'memize';
* @property {string} content Matched content.
* @property {WPShortcode} shortcode Shortcode instance of the match.
*/
+type WPShortcodeMatch = {
+ /**
+ * Index the shortcode is found at.
+ */
+ index: number;
+ /**
+ * Matched content.
+ */
+ content: string;
+ /**
+ * Shortcode instance of the match.
+ */
+ shortcode: WPShortcode;
+};
/**
* Find the next matching shortcode.
@@ -41,7 +83,11 @@ import memize from 'memize';
*
* @return {WPShortcodeMatch | undefined} Matched information.
*/
-export function next( tag, text, index = 0 ) {
+export function next(
+ tag: string,
+ text: string,
+ index = 0
+): WPShortcodeMatch | undefined {
const re = regexp( tag );
re.lastIndex = index;
@@ -88,24 +134,28 @@ export function next( tag, text, index = 0 ) {
*
* @return {string} Text with shortcodes replaced.
*/
-export function replace( tag, text, callback ) {
- return text.replace(
- regexp( tag ),
- function ( match, left, $3, attrs, slash, content, closing, right ) {
- // If both extra brackets exist, the shortcode has been properly
- // escaped.
- if ( left === '[' && right === ']' ) {
- return match;
- }
+export function replace(
+ tag: string,
+ text: string,
+ callback: ( shortcode: WPShortcode ) => any
+): string {
+ return text.replace( regexp( tag ), function ( ...args ) {
+ const [ match, left, , , , , , right ] = args;
+ // If both extra brackets exist, the shortcode has been properly
+ // escaped.
+ if ( left === '[' && right === ']' ) {
+ return match;
+ }
- // Create the match object and pass it through the callback.
- const result = callback( fromMatch( arguments ) );
+ // Create the match object and pass it through the callback.
+ const result = callback(
+ fromMatch( args as unknown as RegExpExecArray )
+ );
- // Make sure to return any of the extra brackets if they weren't used to
- // escape the shortcode.
- return result || result === '' ? left + result + right : match;
- }
- );
+ // Make sure to return any of the extra brackets if they weren't used to
+ // escape the shortcode.
+ return result || result === '' ? left + result + right : match;
+ } );
}
/**
@@ -121,7 +171,7 @@ export function replace( tag, text, callback ) {
*
* @return {string} String representation of the shortcode.
*/
-export function string( options ) {
+export function string( options: WPShortcodeOptions ): string {
return new shortcode( options ).string();
}
@@ -145,7 +195,7 @@ export function string( options ) {
*
* @return {RegExp} Shortcode RegExp.
*/
-export function regexp( tag ) {
+export function regexp( tag: string ): RegExp {
return new RegExp(
'\\[(\\[?)(' +
tag +
@@ -171,9 +221,9 @@ export function regexp( tag ) {
*
* @return {WPShortcodeAttrs} Parsed shortcode attributes.
*/
-export const attrs = memize( ( text ) => {
- const named = {};
- const numeric = [];
+export const attrs = memize( ( text: string ): WPShortcodeAttrs => {
+ const named: Record< string, string > = {};
+ const numeric: string[] = [];
// This regular expression is reused from `shortcode_parse_atts()` in
// `wp-includes/shortcodes.php`.
@@ -195,7 +245,7 @@ export const attrs = memize( ( text ) => {
// Map zero-width spaces to actual spaces.
text = text.replace( /[\u00a0\u200b]/g, ' ' );
- let match;
+ let match: RegExpExecArray | null;
// Match and normalize attributes.
while ( ( match = pattern.exec( text ) ) ) {
@@ -228,8 +278,8 @@ export const attrs = memize( ( text ) => {
*
* @return {WPShortcode} Shortcode instance.
*/
-export function fromMatch( match ) {
- let type;
+export function fromMatch( match: RegExpExecArray ): WPShortcode {
+ let type: 'single' | 'self-closing' | 'closed';
if ( match[ 4 ] ) {
type = 'self-closing';
@@ -247,6 +297,19 @@ export function fromMatch( match ) {
} );
}
+function isWPShortcodeAttrsObject(
+ attributes: WPShortcodeAttrs | WPShortcodeAttrs[ 'named' ]
+): attributes is WPShortcodeAttrs {
+ const attributeTypes = [ 'named', 'numeric' ];
+ return (
+ Object.keys( attributes ).length === attributeTypes.length &&
+ attributeTypes.every(
+ ( attributeType, key ) =>
+ attributeType === Object.keys( attributes )[ key ]
+ )
+ );
+}
+
/**
* Creates a shortcode instance.
*
@@ -259,10 +322,17 @@ export function fromMatch( match ) {
*
* @return {WPShortcode} Shortcode instance.
*/
-const shortcode = Object.assign(
- function ( options ) {
+class shortcode {
+ tag?: string;
+ attrs: WPShortcodeAttrs;
+ type?: 'single' | 'self-closing' | 'closed';
+ content?: string;
+
+ constructor( options: WPShortcodeOptions ) {
const { tag, attrs: attributes, type, content } = options || {};
- Object.assign( this, { tag, type, content } );
+ this.tag = tag;
+ this.type = type;
+ this.content = content;
// Ensure we have a correctly formatted `attrs` object.
this.attrs = {
@@ -274,16 +344,11 @@ const shortcode = Object.assign(
return;
}
- const attributeTypes = [ 'named', 'numeric' ];
-
// Parse a string of attributes.
if ( typeof attributes === 'string' ) {
this.attrs = attrs( attributes );
// Identify a correctly formatted `attrs` object.
- } else if (
- attributes.length === attributeTypes.length &&
- attributeTypes.every( ( t, key ) => t === attributes[ key ] )
- ) {
+ } else if ( isWPShortcodeAttrsObject( attributes ) ) {
this.attrs = attributes;
// Handle a flat object of attributes.
} else {
@@ -291,18 +356,13 @@ const shortcode = Object.assign(
this.set( key, value );
} );
}
- },
- {
- next,
- replace,
- string,
- regexp,
- attrs,
- fromMatch,
}
-);
-Object.assign( shortcode.prototype, {
+ next = next;
+ replace = replace;
+ regexp = regexp;
+ fromMatch = fromMatch;
+
/**
* Get a shortcode attribute.
*
@@ -313,11 +373,12 @@ Object.assign( shortcode.prototype, {
*
* @return {string} Attribute value.
*/
- get( attr ) {
- return this.attrs[ typeof attr === 'number' ? 'numeric' : 'named' ][
- attr
- ];
- },
+ get( attr: number | string ): string {
+ if ( typeof attr === 'number' ) {
+ return this.attrs.numeric[ attr ];
+ }
+ return this.attrs.named[ attr ];
+ }
/**
* Set a shortcode attribute.
@@ -330,18 +391,21 @@ Object.assign( shortcode.prototype, {
*
* @return {WPShortcode} Shortcode instance.
*/
- set( attr, value ) {
- this.attrs[ typeof attr === 'number' ? 'numeric' : 'named' ][ attr ] =
- value;
+ set( attr: number | string, value: string ): WPShortcode {
+ if ( typeof attr === 'number' ) {
+ this.attrs.numeric[ attr ] = value;
+ } else {
+ this.attrs.named[ attr ] = value;
+ }
return this;
- },
+ }
/**
* Transform the shortcode into a string.
*
* @return {string} String representation of the shortcode.
*/
- string() {
+ string(): string {
let text = '[' + this.tag;
this.attrs.numeric.forEach( ( value ) => {
@@ -373,7 +437,9 @@ Object.assign( shortcode.prototype, {
// Add the closing tag.
return text + '[/' + this.tag + ']';
- },
-} );
+ }
+}
+
+type WPShortcode = shortcode;
export default shortcode;
diff --git a/packages/shortcode/src/test/index.js b/packages/shortcode/src/test/index.ts
similarity index 82%
rename from packages/shortcode/src/test/index.js
rename to packages/shortcode/src/test/index.ts
index e385dacb0870b..6039878394996 100644
--- a/packages/shortcode/src/test/index.js
+++ b/packages/shortcode/src/test/index.ts
@@ -1,13 +1,13 @@
/**
* Internal dependencies
*/
-import { next, replace, attrs } from '../';
+import shortcode, { next, replace, attrs } from '..';
describe( 'shortcode', () => {
describe( 'next', () => {
it( 'should find the shortcode', () => {
const result = next( 'foo', 'this has the [foo] shortcode' );
- expect( result.index ).toBe( 13 );
+ expect( result?.index ).toBe( 13 );
} );
it( 'should find the shortcode with attributes', () => {
@@ -15,7 +15,7 @@ describe( 'shortcode', () => {
'foo',
'this has the [foo param="foo"] shortcode'
);
- expect( result.index ).toBe( 13 );
+ expect( result?.index ).toBe( 13 );
} );
it( 'should not find shortcodes that are not there', () => {
@@ -33,10 +33,10 @@ describe( 'shortcode', () => {
it( 'should find the shortcode when told to start looking beyond the start of the string', () => {
const result1 = next( 'foo', 'this has the [foo] shortcode', 12 );
- expect( result1.index ).toBe( 13 );
+ expect( result1?.index ).toBe( 13 );
const result2 = next( 'foo', 'this has the [foo] shortcode', 13 );
- expect( result2.index ).toBe( 13 );
+ expect( result2?.index ).toBe( 13 );
const result3 = next( 'foo', 'this has the [foo] shortcode', 14 );
expect( result3 ).toBe( undefined );
@@ -48,7 +48,7 @@ describe( 'shortcode', () => {
'this has the [foo] shortcode [foo] twice',
14
);
- expect( result.index ).toBe( 29 );
+ expect( result?.index ).toBe( 29 );
} );
it( 'should not find escaped shortcodes', () => {
@@ -66,18 +66,18 @@ describe( 'shortcode', () => {
it( 'should find shortcodes that are incorrectly escaped by newlines', () => {
const result1 = next( 'foo', 'this has the [\n[foo]] shortcode' );
- expect( result1.index ).toBe( 15 );
+ expect( result1?.index ).toBe( 15 );
const result2 = next( 'foo', 'this has the [[foo]\n] shortcode' );
- expect( result2.index ).toBe( 14 );
+ expect( result2?.index ).toBe( 14 );
} );
it( 'should still work when there are not equal amounts of square brackets', () => {
const result1 = next( 'foo', 'this has the [[foo] shortcode' );
- expect( result1.index ).toBe( 14 );
+ expect( result1?.index ).toBe( 14 );
const result2 = next( 'foo', 'this has the [foo]] shortcode' );
- expect( result2.index ).toBe( 13 );
+ expect( result2?.index ).toBe( 13 );
} );
it( 'should find the second instances of the shortcode when the first one is escaped', () => {
@@ -85,7 +85,7 @@ describe( 'shortcode', () => {
'foo',
'this has the [[foo]] shortcode [foo] twice'
);
- expect( result.index ).toBe( 31 );
+ expect( result?.index ).toBe( 31 );
} );
it( 'should not find shortcodes that are not full matches', () => {
@@ -306,4 +306,62 @@ describe( 'shortcode', () => {
expect( result ).toEqual( expected );
} );
} );
+
+ describe( 'WPShortcode', () => {
+ it( 'should accept a string as the attribute type', () => {
+ const result = new shortcode( {
+ tag: '',
+ content: '',
+ type: 'single',
+ attrs: `param="foo" another='bar' andagain=baz`,
+ } ).attrs;
+ const expected = {
+ named: {
+ param: 'foo',
+ another: 'bar',
+ andagain: 'baz',
+ },
+ numeric: [],
+ };
+ expect( result ).toEqual( expected );
+ } );
+ it( 'should accept a valid object as the attribute type', () => {
+ const result = new shortcode( {
+ tag: '',
+ content: '',
+ type: 'single',
+ attrs: attrs( `param="foo" another='bar' andagain=baz` ),
+ } ).attrs;
+ const expected = {
+ named: {
+ param: 'foo',
+ another: 'bar',
+ andagain: 'baz',
+ },
+ numeric: [],
+ };
+ expect( result ).toEqual( expected );
+ } );
+ it( 'should accept a shallow named object as the attribute type', () => {
+ const result = new shortcode( {
+ tag: '',
+ content: '',
+ type: 'single',
+ attrs: {
+ param: 'foo',
+ another: 'bar',
+ andagain: 'baz',
+ },
+ } ).attrs;
+ const expected = {
+ named: {
+ param: 'foo',
+ another: 'bar',
+ andagain: 'baz',
+ },
+ numeric: [],
+ };
+ expect( result ).toEqual( expected );
+ } );
+ } );
} );
diff --git a/packages/shortcode/tsconfig.json b/packages/shortcode/tsconfig.json
new file mode 100644
index 0000000000000..24d6470ee8589
--- /dev/null
+++ b/packages/shortcode/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig.json",
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "declarationDir": "build-types"
+ },
+ "includes": [ "src/**/*" ]
+}
diff --git a/tsconfig.json b/tsconfig.json
index a273bd22806a8..298550c245f93 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -44,6 +44,7 @@
{ "path": "packages/redux-routine" },
{ "path": "packages/report-flaky-tests" },
{ "path": "packages/rich-text" },
+ { "path": "packages/shortcode" },
{ "path": "packages/style-engine" },
{ "path": "packages/sync" },
{ "path": "packages/token-list" },
From 677861bcd5f6632feb974c03345a099091b6f2eb Mon Sep 17 00:00:00 2001
From: Mikey Binns
Date: Sat, 6 Apr 2024 01:36:32 +0100
Subject: [PATCH 002/600] add ShortcodeType to ease maintaining
---
packages/shortcode/src/index.ts | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/shortcode/src/index.ts b/packages/shortcode/src/index.ts
index 8a43c32140776..190bf8598245b 100644
--- a/packages/shortcode/src/index.ts
+++ b/packages/shortcode/src/index.ts
@@ -22,6 +22,8 @@ type WPShortcodeAttrs = {
numeric: string[];
};
+type ShortcodeType = 'single' | 'self-closing' | 'closed';
+
/**
* Shortcode object.
*
@@ -49,7 +51,7 @@ type WPShortcodeOptions = {
/**
* Shortcode type.
*/
- type: 'single' | 'self-closing' | 'closed';
+ type: ShortcodeType;
};
/**
@@ -279,7 +281,7 @@ export const attrs = memize( ( text: string ): WPShortcodeAttrs => {
* @return {WPShortcode} Shortcode instance.
*/
export function fromMatch( match: RegExpExecArray ): WPShortcode {
- let type: 'single' | 'self-closing' | 'closed';
+ let type: ShortcodeType;
if ( match[ 4 ] ) {
type = 'self-closing';
@@ -325,7 +327,7 @@ function isWPShortcodeAttrsObject(
class shortcode {
tag?: string;
attrs: WPShortcodeAttrs;
- type?: 'single' | 'self-closing' | 'closed';
+ type?: ShortcodeType;
content?: string;
constructor( options: WPShortcodeOptions ) {
From d0383fef7532779a27afbd226c51b535232201d4 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Sun, 1 Dec 2024 02:04:43 +0000
Subject: [PATCH 003/600] Fix: Styles section does not moves stylebook to
typography. (#67423)
Co-authored-by: jorgefilipecosta
Co-authored-by: ramonjd
---
packages/edit-site/src/components/style-book/index.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/packages/edit-site/src/components/style-book/index.js b/packages/edit-site/src/components/style-book/index.js
index de4c38bd40c05..6a044d8055300 100644
--- a/packages/edit-site/src/components/style-book/index.js
+++ b/packages/edit-site/src/components/style-book/index.js
@@ -89,6 +89,11 @@ const scrollToSection = ( anchorId, iframe ) => {
*/
const getStyleBookNavigationFromPath = ( path ) => {
if ( path && typeof path === 'string' ) {
+ if ( path.startsWith( '/typography' ) ) {
+ return {
+ block: 'typography',
+ };
+ }
let block = path.includes( '/blocks/' )
? decodeURIComponent( path.split( '/blocks/' )[ 1 ] )
: null;
From 988f259c5c32d312bd09a25789c6b658069ccb90 Mon Sep 17 00:00:00 2001
From: Shail Mehta
Date: Sun, 1 Dec 2024 19:38:30 +0530
Subject: [PATCH 004/600] Updated old URL in Documentation (#67446)
* Updated Old URL
Co-authored-by: shail-mehta
Co-authored-by: Mamaduka
---
docs/reference-guides/block-api/block-transforms.md | 12 ++++++------
platform-docs/docs/create-block/transforms.md | 2 +-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/reference-guides/block-api/block-transforms.md b/docs/reference-guides/block-api/block-transforms.md
index c2c5ed49d1b19..9055ed0a3b45b 100644
--- a/docs/reference-guides/block-api/block-transforms.md
+++ b/docs/reference-guides/block-api/block-transforms.md
@@ -44,7 +44,7 @@ A transformation of type `block` is an object that takes the following parameter
- **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects.
- **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user.
- **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If true, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. False by default.
-- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
+- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: from Paragraph block to Heading block**
@@ -97,7 +97,7 @@ A transformation of type `enter` is an object that takes the following parameter
- **type** _(string)_: the value `enter`.
- **regExp** _(RegExp)_: the Regular Expression to use as a matcher. If the value matches, the transformation will be applied.
- **transform** _(function)_: a callback that receives an object with a `content` field containing the value that has been entered. It should return a block object or an array of block objects.
-- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
+- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: from --- to Separator block**
@@ -124,7 +124,7 @@ A transformation of type `files` is an object that takes the following parameter
- **type** _(string)_: the value `files`.
- **transform** _(function)_: a callback that receives the array of files being processed. It should return a block object or an array of block objects.
- **isMatch** _(function, optional)_: a callback that receives the array of files being processed and should return a boolean. Returning `false` from this function will prevent the transform from being applied.
-- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
+- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: from file to File block**
@@ -164,7 +164,7 @@ A transformation of type `prefix` is an object that takes the following paramete
- **type** _(string)_: the value `prefix`.
- **prefix** _(string)_: the character or sequence of characters that match this transform.
- **transform** _(function)_: a callback that receives the content introduced. It should return a block object or an array of block objects.
-- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
+- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: from text to custom block**
@@ -197,7 +197,7 @@ A transformation of type `raw` is an object that takes the following parameters:
- **schema** _(object|function, optional)_: defines an [HTML content model](https://html.spec.whatwg.org/multipage/dom.html#content-models) used to detect and process pasted contents. See [below](#schemas-and-content-models).
- **selector** _(string, optional)_: a CSS selector string to determine whether the element matches according to the [element.matches](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) method. The transform won't be executed if the element doesn't match. This is a shorthand and alternative to using `isMatch`, which, if present, will take precedence.
- **isMatch** _(function, optional)_: a callback that receives the node being processed and should return a boolean. Returning `false` from this function will prevent the transform from being applied.
-- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
+- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: from URLs to Embed block**
@@ -273,7 +273,7 @@ A transformation of type `shortcode` is an object that takes the following param
- **transform** _(function, optional)_: a callback that receives the shortcode attributes as the first argument and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as the second. It should return a block object or an array of block objects. When this parameter is defined, it will take precedence over the `attributes` parameter.
- **attributes** _(object, optional)_: object representing where the block attributes should be sourced from, according to the attributes shape defined by the [block configuration object](./block-registration.md). If a particular attribute contains a `shortcode` key, it should be a function that receives the shortcode attributes as the first arguments and the [WPShortcodeMatch](/packages/shortcode/README.md#next) as second, and returns a value for the attribute that will be sourced in the block's comment.
- **isMatch** _(function, optional)_: a callback that receives the shortcode attributes per the [Shortcode API](https://codex.wordpress.org/Shortcode_API) and should return a boolean. Returning `false` from this function will prevent the shortcode to be transformed into this block.
-- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
+- **priority** _(number, optional)_: controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: from shortcode to block using `transform`**
diff --git a/platform-docs/docs/create-block/transforms.md b/platform-docs/docs/create-block/transforms.md
index fd235b669cd72..4a4118d6850b9 100644
--- a/platform-docs/docs/create-block/transforms.md
+++ b/platform-docs/docs/create-block/transforms.md
@@ -37,7 +37,7 @@ A transformation of type `block` is an object that takes the following parameter
- **transform** _(function)_: a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects.
- **isMatch** _(function, optional)_: a callback that receives the block attributes as the first argument and the block object as the second argument and should return a boolean. Returning `false` from this function will prevent the transform from being available and displayed as an option to the user.
- **isMultiBlock** _(boolean, optional)_: whether the transformation can be applied when multiple blocks are selected. If `true`, the `transform` function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. Returns `false` by default.
-- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://codex.wordpress.org/Plugin_API#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
+- **priority** _(number, optional)_: controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a [WordPress hook](https://developer.wordpress.org/reference/#Hook_to_WordPress). Like hooks, the default priority is `10` when not otherwise set.
**Example: Let's declare a transform from our Gutenpride block to Heading block**
From 4775e7052b9e2ed7df46429e6e738de3faf2fb18 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Sun, 1 Dec 2024 20:00:10 +0000
Subject: [PATCH 005/600] Preload: fix settings fields order (#67450)
Co-authored-by: ellatrix
Co-authored-by: Mamaduka
---
lib/compat/wordpress-6.8/preload.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php
index ae6c738c6627c..6d92913b41411 100644
--- a/lib/compat/wordpress-6.8/preload.php
+++ b/lib/compat/wordpress-6.8/preload.php
@@ -31,9 +31,9 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) {
'site_icon_url',
'site_logo',
'timezone_string',
- 'url',
'default_template_part_areas',
'default_template_types',
+ 'url',
)
);
$paths[] = '/wp/v2/templates/lookup?slug=front-page';
From c5b33b86f27dd6b6e3b4450b7c72cb949e4bd95b Mon Sep 17 00:00:00 2001
From: Sunil Prajapati <61308756+akasunil@users.noreply.github.com>
Date: Mon, 2 Dec 2024 10:59:13 +0530
Subject: [PATCH 006/600] Implement image size option for featured image in
cover block (#67273)
Co-authored-by: akasunil
Co-authored-by: aaronrobertshaw
Co-authored-by: carolinan
---
packages/block-library/src/cover/edit/index.js | 5 ++++-
.../src/cover/edit/inspector-controls.js | 13 ++++++++++---
packages/block-library/src/cover/index.php | 4 ++--
3 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js
index 3ad7039b55892..ced3097320329 100644
--- a/packages/block-library/src/cover/edit/index.js
+++ b/packages/block-library/src/cover/edit/index.js
@@ -120,7 +120,9 @@ function CoverEdit( {
select( coreStore ).getMedia( featuredImage, { context: 'view' } ),
[ featuredImage ]
);
- const mediaUrl = media?.source_url;
+ const mediaUrl =
+ media?.media_details?.sizes?.[ sizeSlug ]?.source_url ??
+ media?.source_url;
// User can change the featured image outside of the block, but we still
// need to update the block when that happens. This effect should only
@@ -451,6 +453,7 @@ function CoverEdit( {
toggleUseFeaturedImage={ toggleUseFeaturedImage }
updateDimRatio={ onUpdateDimRatio }
onClearMedia={ onClearMedia }
+ featuredImage={ media }
/>
);
diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js
index c0807869ee1a5..b0d4b435163b7 100644
--- a/packages/block-library/src/cover/edit/inspector-controls.js
+++ b/packages/block-library/src/cover/edit/inspector-controls.js
@@ -96,6 +96,7 @@ export default function CoverInspectorControls( {
coverRef,
currentSettings,
updateDimRatio,
+ featuredImage,
} ) {
const {
useFeaturedImage,
@@ -132,8 +133,12 @@ export default function CoverInspectorControls( {
[ id, isImageBackground ]
);
+ const currentBackgroundImage = useFeaturedImage ? featuredImage : image;
+
function updateImage( newSizeSlug ) {
- const newUrl = image?.media_details?.sizes?.[ newSizeSlug ]?.source_url;
+ const newUrl =
+ currentBackgroundImage?.media_details?.sizes?.[ newSizeSlug ]
+ ?.source_url;
if ( ! newUrl ) {
return null;
}
@@ -146,7 +151,9 @@ export default function CoverInspectorControls( {
const imageSizeOptions = imageSizes
?.filter(
- ( { slug } ) => image?.media_details?.sizes?.[ slug ]?.source_url
+ ( { slug } ) =>
+ currentBackgroundImage?.media_details?.sizes?.[ slug ]
+ ?.source_url
)
?.map( ( { name, slug } ) => ( { value: slug, label: name } ) );
@@ -321,7 +328,7 @@ export default function CoverInspectorControls( {
/>
) }
- { ! useFeaturedImage && !! imageSizeOptions?.length && (
+ { !! imageSizeOptions?.length && (
Date: Mon, 2 Dec 2024 14:43:15 +0800
Subject: [PATCH 007/600] Fix List View not updating when switching editor
modes (#67379)
* Add back the editorTool dependency to getEnabledClientIdsTree
* Add e2e test
* Switch to getEditorMode selector
----
Co-authored-by: talldan
Co-authored-by: aaronrobertshaw
---
.../src/store/private-selectors.js | 8 +--
.../editor/various/write-design-mode.spec.js | 55 +++++++++++++++++++
2 files changed, 59 insertions(+), 4 deletions(-)
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index 9779ae1300fb5..61b17a3625d15 100644
--- a/packages/block-editor/src/store/private-selectors.js
+++ b/packages/block-editor/src/store/private-selectors.js
@@ -109,16 +109,16 @@ function getEnabledClientIdsTreeUnmemoized( state, rootClientId ) {
*
* @return {Object[]} Tree of block objects with only clientID and innerBlocks set.
*/
-export const getEnabledClientIdsTree = createSelector(
- getEnabledClientIdsTreeUnmemoized,
- ( state ) => [
+export const getEnabledClientIdsTree = createRegistrySelector( ( select ) =>
+ createSelector( getEnabledClientIdsTreeUnmemoized, ( state ) => [
state.blocks.order,
state.derivedBlockEditingModes,
state.derivedNavModeBlockEditingModes,
state.blockEditingModes,
state.settings.templateLock,
state.blockListSettings,
- ]
+ select( STORE_NAME ).__unstableGetEditorMode( state ),
+ ] )
);
/**
diff --git a/test/e2e/specs/editor/various/write-design-mode.spec.js b/test/e2e/specs/editor/various/write-design-mode.spec.js
index 053f4cb8ff092..2892b4aea89e9 100644
--- a/test/e2e/specs/editor/various/write-design-mode.spec.js
+++ b/test/e2e/specs/editor/various/write-design-mode.spec.js
@@ -121,4 +121,59 @@ test.describe( 'Write/Design mode', () => {
editorSettings.getByRole( 'button', { name: 'Content' } )
).toBeVisible();
} );
+
+ test( 'hides the blocks that cannot be interacted with in List View', async ( {
+ editor,
+ page,
+ pageUtils,
+ } ) => {
+ await editor.setContent( '' );
+
+ // Insert a section with a nested block and an editable block.
+ await editor.insertBlock( {
+ name: 'core/group',
+ attributes: {},
+ innerBlocks: [
+ {
+ name: 'core/group',
+ attributes: {
+ metadata: {
+ name: 'Non-content block',
+ },
+ },
+ innerBlocks: [
+ {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'Something',
+ },
+ },
+ ],
+ },
+ ],
+ } );
+
+ // Select the inner paragraph block so that List View is expanded.
+ await editor.canvas
+ .getByRole( 'document', {
+ name: 'Block: Paragraph',
+ } )
+ .click();
+
+ // Open List View.
+ await pageUtils.pressKeys( 'access+o' );
+ const listView = page.getByRole( 'treegrid', {
+ name: 'Block navigation structure',
+ } );
+ const nonContentBlock = listView.getByRole( 'link', {
+ name: 'Non-content block',
+ } );
+
+ await expect( nonContentBlock ).toBeVisible();
+
+ // Switch to write mode.
+ await editor.switchEditorTool( 'Write' );
+
+ await expect( nonContentBlock ).toBeHidden();
+ } );
} );
From d251f755481bdfe6eac99ab85f6410e3334622cc Mon Sep 17 00:00:00 2001
From: Eshaan Dabasiya <76681468+im3dabasia@users.noreply.github.com>
Date: Mon, 2 Dec 2024 14:10:03 +0530
Subject: [PATCH 008/600] Experiments: Remove trailing space in Color
randomizer (#67457)
Co-authored-by: im3dabasia
Co-authored-by: ramonjd
---
lib/experiments-page.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/experiments-page.php b/lib/experiments-page.php
index 9033e3c2d0c1f..256a185a3af69 100644
--- a/lib/experiments-page.php
+++ b/lib/experiments-page.php
@@ -69,7 +69,7 @@ function gutenberg_initialize_experiments_settings() {
add_settings_field(
'gutenberg-color-randomizer',
- __( 'Color randomizer ', 'gutenberg' ),
+ __( 'Color randomizer', 'gutenberg' ),
'gutenberg_display_experiment_field',
'gutenberg-experiments',
'gutenberg_experiments_section',
From 2f670d56405a6c43800e5a5068bb8ee75b9205d9 Mon Sep 17 00:00:00 2001
From: Yogesh Bhutkar
Date: Mon, 2 Dec 2024 15:30:08 +0530
Subject: [PATCH 009/600] Remove inline-block display from image anchor in
style.scss (#67368)
* Remove inline-block display from image anchor in style.scss
* Refactor: Set image anchor display to inline-block in style.scss
Co-authored-by: yogeshbhutkar
Co-authored-by: t-hamano
Co-authored-by: Infinite-Null
Co-authored-by: hanneslsm
Co-authored-by: frkly
---
packages/block-library/src/image/style.scss | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss
index a7fcb8f175e4e..8ca5795cfd911 100644
--- a/packages/block-library/src/image/style.scss
+++ b/packages/block-library/src/image/style.scss
@@ -1,6 +1,7 @@
.wp-block-image {
- a {
+ > a,
+ > figure > a {
display: inline-block;
}
From 358fb8e04445ebf29140ba875e273bba8fd43913 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 2 Dec 2024 10:07:39 +0000
Subject: [PATCH 010/600] Inserter: Patterns: remove loading indicator (#67072)
---
.../inserter/block-patterns-tab/index.js | 18 +-----------------
.../src/store/private-selectors.js | 15 ---------------
2 files changed, 1 insertion(+), 32 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js
index 01e41111b7c89..45db4732aa9c6 100644
--- a/packages/block-editor/src/components/inserter/block-patterns-tab/index.js
+++ b/packages/block-editor/src/components/inserter/block-patterns-tab/index.js
@@ -3,9 +3,8 @@
*/
import { useState } from '@wordpress/element';
import { useViewportMatch } from '@wordpress/compose';
-import { Button, Spinner } from '@wordpress/components';
+import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -16,8 +15,6 @@ import { PatternCategoryPreviews } from './pattern-category-previews';
import { usePatternCategories } from './use-pattern-categories';
import CategoryTabs from '../category-tabs';
import InserterNoResults from '../no-results';
-import { store as blockEditorStore } from '../../../store';
-import { unlock } from '../../../lock-unlock';
function BlockPatternsTab( {
onSelectCategory,
@@ -31,19 +28,6 @@ function BlockPatternsTab( {
const categories = usePatternCategories( rootClientId );
const isMobile = useViewportMatch( 'medium', '<' );
- const isResolvingPatterns = useSelect(
- ( select ) =>
- unlock( select( blockEditorStore ) ).isResolvingPatterns(),
- []
- );
-
- if ( isResolvingPatterns ) {
- return (
-
-
-
- );
- }
if ( ! categories.length ) {
return ;
diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js
index 61b17a3625d15..c46778d889b3e 100644
--- a/packages/block-editor/src/store/private-selectors.js
+++ b/packages/block-editor/src/store/private-selectors.js
@@ -406,21 +406,6 @@ export const getAllPatterns = createRegistrySelector( ( select ) =>
}, getAllPatternsDependants( select ) )
);
-export const isResolvingPatterns = createRegistrySelector( ( select ) =>
- createSelector( ( state ) => {
- const blockPatternsSelect = state.settings[ selectBlockPatternsKey ];
- const reusableBlocksSelect = state.settings[ reusableBlocksSelectKey ];
- return (
- ( blockPatternsSelect
- ? blockPatternsSelect( select ) === undefined
- : false ) ||
- ( reusableBlocksSelect
- ? reusableBlocksSelect( select ) === undefined
- : false )
- );
- }, getAllPatternsDependants( select ) )
-);
-
const EMPTY_ARRAY = [];
export const getReusableBlocks = createRegistrySelector(
From 22c43ff9e687eb5752b790316f2b1eeb99976dbb Mon Sep 17 00:00:00 2001
From: Dave Smith
Date: Mon, 2 Dec 2024 10:40:27 +0000
Subject: [PATCH 011/600] Disable Zoom Out if no section root to allow for
Theme opt in (#67232)
* Disable toolbar and auto inserter behaviour if no section root
* Remove useless coercion
Co-authored-by: Ramon
* Remove more coercion copy/paste
Co-authored-by: Ramon
* Add section root to Zoom Out e2e test
* Add test coverage
* Add some test coverage
* Try e2e test fix by reverting all template part changes in Theme
* Remove need to exit Zoom Out
* Ensure a main tag
* Update tests to expect the click-through behavior
* Simplify selection
---------
Co-authored-by: getdave
Co-authored-by: talldan
Co-authored-by: jeryj
Co-authored-by: ramonjd
Co-authored-by: draganescu
---
.../src/components/inserter/menu.js | 9 ++-
.../editor/src/components/header/index.js | 16 ++++-
.../editor/various/parsing-patterns.spec.js | 3 +-
.../editor/various/pattern-overrides.spec.js | 70 ++++++++++++++++---
test/e2e/specs/site-editor/zoom-out.spec.js | 45 +++++++++++-
5 files changed, 127 insertions(+), 16 deletions(-)
diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js
index 8dc2f64063c8e..ef260beb85906 100644
--- a/packages/block-editor/src/components/inserter/menu.js
+++ b/packages/block-editor/src/components/inserter/menu.js
@@ -58,6 +58,11 @@ function InserterMenu(
( select ) => unlock( select( blockEditorStore ) ).isZoomOut(),
[]
);
+ const hasSectionRootClientId = useSelect(
+ ( select ) =>
+ !! unlock( select( blockEditorStore ) ).getSectionRootClientId(),
+ []
+ );
const [ filterValue, setFilterValue, delayedFilterValue ] =
useDebouncedInput( __experimentalFilterValue );
const [ hoveredItem, setHoveredItem ] = useState( null );
@@ -81,7 +86,9 @@ function InserterMenu(
const [ selectedTab, setSelectedTab ] = useState( getInitialTab() );
const shouldUseZoomOut =
- selectedTab === 'patterns' || selectedTab === 'media';
+ hasSectionRootClientId &&
+ ( selectedTab === 'patterns' || selectedTab === 'media' );
+
useZoomOut( shouldUseZoomOut && isLargeViewport );
const [ destinationRootClientId, onInsertBlocks, onToggleInsertionPoint ] =
diff --git a/packages/editor/src/components/header/index.js b/packages/editor/src/components/header/index.js
index 51c341f2c1bd1..79199b15b1ad1 100644
--- a/packages/editor/src/components/header/index.js
+++ b/packages/editor/src/components/header/index.js
@@ -30,6 +30,7 @@ import {
PATTERN_POST_TYPE,
NAVIGATION_POST_TYPE,
} from '../../store/constants';
+import { unlock } from '../../lock-unlock';
const toolbarVariations = {
distractionFreeDisabled: { y: '-50px' },
@@ -102,6 +103,13 @@ function Header( {
( hasFixedToolbar &&
( ! hasBlockSelection || isBlockToolsCollapsed ) ) );
const hasBackButton = useHasBackButton();
+
+ const hasSectionRootClientId = useSelect(
+ ( select ) =>
+ !! unlock( select( blockEditorStore ) ).getSectionRootClientId(),
+ []
+ );
+
/*
* The edit-post-header classname is only kept for backward compatability
* as some plugins might be relying on its presence.
@@ -169,9 +177,11 @@ function Header( {
forceIsAutosaveable={ forceIsDirty }
/>
- { canBeZoomedOut && isWideViewport && (
-
- ) }
+ { canBeZoomedOut &&
+ isWideViewport &&
+ hasSectionRootClientId && (
+
+ ) }
{ ( isWideViewport || ! showIconLabels ) && (
diff --git a/test/e2e/specs/editor/various/parsing-patterns.spec.js b/test/e2e/specs/editor/various/parsing-patterns.spec.js
index d8edc544ffa03..98261804acb58 100644
--- a/test/e2e/specs/editor/various/parsing-patterns.spec.js
+++ b/test/e2e/specs/editor/various/parsing-patterns.spec.js
@@ -37,9 +37,8 @@ test.describe( 'Parsing patterns', () => {
} );
} );
- // Exit zoom out mode and select the inner buttons block to ensure
+ // Select the inner buttons block to ensure
// the correct insertion point is selected.
- await page.getByRole( 'button', { name: 'Zoom Out' } ).click();
await editor.selectBlocks(
editor.canvas.locator( 'role=document[name="Block: Button"i]' )
);
diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js
index 7069b4cec258a..7d2be84187ef6 100644
--- a/test/e2e/specs/editor/various/pattern-overrides.spec.js
+++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js
@@ -268,12 +268,25 @@ test.describe( 'Pattern Overrides', () => {
} );
await editor.setContent( '' );
+ await editor.switchEditorTool( 'Design' );
+ // Insert a `` group block.
+ // In zoomed out and write mode it acts as the section root.
+ // Inside is a pattern that acts as a section.
await editor.insertBlock( {
- name: 'core/block',
- attributes: { ref: id },
+ name: 'core/group',
+ attributes: { tagName: 'main' },
+ innerBlocks: [
+ {
+ name: 'core/block',
+ attributes: { ref: id },
+ },
+ ],
} );
+ const groupBlock = editor.canvas.getByRole( 'document', {
+ name: 'Block: Group',
+ } );
const patternBlock = editor.canvas.getByRole( 'document', {
name: 'Block: Pattern',
} );
@@ -290,14 +303,35 @@ test.describe( 'Pattern Overrides', () => {
hasText: 'No Overrides or Binding',
} );
- await test.step( 'Zoomed in / Design mode', async () => {
- await editor.switchEditorTool( 'Design' );
- // In zoomed in and design mode the pattern block and child blocks
- // with bindings are editable.
+ await test.step( 'Click-through behavior', async () => {
+ // With the group block selected, all the inner blocks of the pattern
+ // are inert due to the 'click-through' behavior, that requires the
+ // pattern block be selected first before its inner blocks are selectable.
+ await editor.selectBlocks( groupBlock );
await expect( patternBlock ).not.toHaveAttribute(
'inert',
'true'
);
+ await expect( blockWithOverrides ).toHaveAttribute(
+ 'inert',
+ 'true'
+ );
+ await expect( blockWithBindings ).toHaveAttribute(
+ 'inert',
+ 'true'
+ );
+ await expect( blockWithoutOverridesOrBindings ).toHaveAttribute(
+ 'inert',
+ 'true'
+ );
+ } );
+
+ await test.step( 'Zoomed in / Design mode', async () => {
+ await editor.selectBlocks( patternBlock );
+
+ // Once selected and in zoomed in/design mode the child blocks
+ // of the pattern with bindings are editable, but unbound
+ // blocks are inert.
await expect( blockWithOverrides ).not.toHaveAttribute(
'inert',
'true'
@@ -314,11 +348,16 @@ test.describe( 'Pattern Overrides', () => {
await test.step( 'Zoomed in / Write mode - pattern as a section', async () => {
await editor.switchEditorTool( 'Write' );
+
// The pattern block is still editable as a section.
await expect( patternBlock ).not.toHaveAttribute(
'inert',
'true'
);
+
+ // Ensure the pattern block is selected.
+ await editor.selectBlocks( patternBlock );
+
// Child blocks of the pattern with bindings are editable.
await expect( blockWithOverrides ).not.toHaveAttribute(
'inert',
@@ -336,11 +375,18 @@ test.describe( 'Pattern Overrides', () => {
await test.step( 'Zoomed out / Write mode - pattern as a section', async () => {
await page.getByLabel( 'Zoom Out' ).click();
- // In zoomed out only the pattern block is editable, as in this scenario it's a section.
+ // In zoomed out only the pattern block is editable,
+ // as in this scenario it's a section.
await expect( patternBlock ).not.toHaveAttribute(
'inert',
'true'
);
+
+ // Ensure the pattern block is selected before checking the child blocks
+ // to ensure the click-through behavior isn't interfering.
+ await editor.selectBlocks( patternBlock );
+
+ // None of the child blocks are editable in zoomed out mode.
await expect( blockWithOverrides ).toHaveAttribute(
'inert',
'true'
@@ -357,11 +403,17 @@ test.describe( 'Pattern Overrides', () => {
await test.step( 'Zoomed out / Design mode - pattern as a section', async () => {
await editor.switchEditorTool( 'Design' );
- // In zoomed out only the pattern block is editable, as in this scenario it's a section.
+ // In zoomed out only the pattern block is editable,
+ // as in this scenario it's a section.
await expect( patternBlock ).not.toHaveAttribute(
'inert',
'true'
);
+
+ // Ensure the pattern block is selected before checking the child blocks
+ // to ensure the click-through behavior isn't interfering.
+ await editor.selectBlocks( patternBlock );
+
await expect( blockWithOverrides ).toHaveAttribute(
'inert',
'true'
@@ -376,7 +428,7 @@ test.describe( 'Pattern Overrides', () => {
);
} );
- // Zoom out and group the pattern.
+ // Zoom out and group the pattern so that it's no longer a section.
await page.getByLabel( 'Zoom Out' ).click();
await editor.selectBlocks( patternBlock );
await editor.clickBlockOptionsMenuItem( 'Group' );
diff --git a/test/e2e/specs/site-editor/zoom-out.spec.js b/test/e2e/specs/site-editor/zoom-out.spec.js
index e698a94b7cf0d..77d121e199939 100644
--- a/test/e2e/specs/site-editor/zoom-out.spec.js
+++ b/test/e2e/specs/site-editor/zoom-out.spec.js
@@ -4,7 +4,8 @@
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
const EDITOR_ZOOM_OUT_CONTENT = `
-
+
+
First Section Start
@@ -58,6 +59,21 @@ const EDITOR_ZOOM_OUT_CONTENT = `
Fourth Section End
+
+`;
+
+const EDITOR_ZOOM_OUT_CONTENT_NO_SECTION_ROOT = `
+
+
First Section Start
+
+
+
+
First Section Center
+
+
+
+
First Section End
+
`;
test.describe( 'Zoom Out', () => {
@@ -67,6 +83,8 @@ test.describe( 'Zoom Out', () => {
test.afterAll( async ( { requestUtils } ) => {
await requestUtils.activateTheme( 'twentytwentyone' );
+ await requestUtils.deleteAllTemplates( 'wp_template' );
+ await requestUtils.deleteAllTemplates( 'wp_template_part' );
} );
test.beforeEach( async ( { admin } ) => {
@@ -215,4 +233,29 @@ test.describe( 'Zoom Out', () => {
await expect( thirdSectionEnd ).toBeInViewport();
await expect( fourthSectionStart ).not.toBeInViewport();
} );
+
+ test( 'Zoom Out cannot be activated when the section root is missing', async ( {
+ page,
+ editor,
+ } ) => {
+ await editor.setContent( EDITOR_ZOOM_OUT_CONTENT_NO_SECTION_ROOT );
+
+ // Check that the Zoom Out toggle button is not visible.
+ await expect(
+ page.getByRole( 'button', { name: 'Zoom Out' } )
+ ).toBeHidden();
+
+ // Check that activating the Patterns tab in the Inserter does not activate
+ // Zoom Out.
+ await page
+ .getByRole( 'button', {
+ name: 'Block Inserter',
+ exact: true,
+ } )
+ .click();
+
+ await page.getByRole( 'tab', { name: 'Patterns' } ).click();
+
+ await expect( page.locator( '.is-zoomed-out' ) ).toBeHidden();
+ } );
} );
From e92d57743b68753ee47f9c21645524e2a5b86ea4 Mon Sep 17 00:00:00 2001
From: Andrea Fercia
Date: Mon, 2 Dec 2024 11:47:20 +0100
Subject: [PATCH 012/600] Remove one occurrence of incorrect usage of
ItemGroup. (#67427)
* Remove one occurrence of incorret usage of ItemGroup.
* Fix toggling visibility of the help text.
Co-authored-by: afercia
Co-authored-by: gziolo
Co-authored-by: Mamaduka
Co-authored-by: SantosGuillamot
---
packages/block-editor/src/hooks/block-bindings.js | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js
index 615804a311c0f..e10696cc1257d 100644
--- a/packages/block-editor/src/hooks/block-bindings.js
+++ b/packages/block-editor/src/hooks/block-bindings.js
@@ -300,13 +300,17 @@ export const BlockBindingsPanel = ( { name: blockName, metadata } ) => {
/>
) }
-
-
+ { /*
+ Use a div element to make the ToolsPanelHiddenInnerWrapper
+ toggle the visibility of this help text automatically.
+ */ }
+
+
{ __(
'Attributes connected to custom fields or other dynamic data.'
) }
-
-
+
+
);
From aef323a70e74fcf676036139bd25f657ad2a0b02 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 2 Dec 2024 10:59:01 +0000
Subject: [PATCH 013/600] PR template: add before/after table (#62739)
Co-authored-by: ellatrix
Co-authored-by: t-hamano
---
.github/PULL_REQUEST_TEMPLATE.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index fd63e5e2e5312..69fd34d709bdc 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -20,3 +20,9 @@ https://github.com/WordPress/gutenberg/blob/trunk/CONTRIBUTING.md -->
## Screenshots or screencast
+
+
+
+|Before|After|
+|-|-|
+|||
From 8d343d155c7577d46aea33e708dceb39c571cc80 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Mon, 2 Dec 2024 12:26:27 +0100
Subject: [PATCH 014/600] Site Editor: Fix focus mode navigation (#67458)
Co-authored-by: youknowriad
Co-authored-by: Mamaduka
Co-authored-by: carolinan
---
.../use-navigate-to-entity-record.js | 2 +-
packages/router/src/router.tsx | 1 +
.../template-part-focus-mode.spec.js | 50 +++++++++++++++++++
3 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 test/e2e/specs/site-editor/template-part-focus-mode.spec.js
diff --git a/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js b/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js
index 8cc7fdaefe2d9..66be70fcd4e2e 100644
--- a/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js
+++ b/packages/edit-site/src/components/block-editor/use-navigate-to-entity-record.js
@@ -17,7 +17,7 @@ export default function useNavigateToEntityRecord() {
const onNavigateToEntityRecord = useCallback(
( params ) => {
history.navigate(
- `/${ params.postType }/${ params.id }?canvas=edit&focusMode=true`
+ `/${ params.postType }/${ params.postId }?canvas=edit&focusMode=true`
);
},
[ history ]
diff --git a/packages/router/src/router.tsx b/packages/router/src/router.tsx
index 34cc542c7b573..2ac7974b4dfbc 100644
--- a/packages/router/src/router.tsx
+++ b/packages/router/src/router.tsx
@@ -146,6 +146,7 @@ export function useHistory() {
return useMemo(
() => ( {
navigate,
+ back: history.back,
} ),
[ navigate ]
);
diff --git a/test/e2e/specs/site-editor/template-part-focus-mode.spec.js b/test/e2e/specs/site-editor/template-part-focus-mode.spec.js
new file mode 100644
index 0000000000000..29e6788779ed9
--- /dev/null
+++ b/test/e2e/specs/site-editor/template-part-focus-mode.spec.js
@@ -0,0 +1,50 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.describe( 'Template Part Focus mode', () => {
+ test.beforeAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyfour' );
+ } );
+
+ test.afterEach( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyone' );
+ } );
+
+ test( 'Should navigate to template part and back.', async ( {
+ admin,
+ page,
+ editor,
+ } ) => {
+ await admin.visitAdminPage( 'site-editor.php?canvas=edit' );
+ await editor.setPreferences( 'core/edit-site', {
+ welcomeGuide: false,
+ } );
+
+ // Check that we're editing the template
+ await expect( page.locator( 'h1' ) ).toContainText( 'Blog Home' );
+ await expect( page.locator( 'h1' ) ).toContainText( 'Template' );
+
+ // Click Template Part
+ await editor.canvas
+ .getByRole( 'document', {
+ name: 'Header',
+ } )
+ .click();
+
+ // Navigate to Focus mode
+ await editor.clickBlockToolbarButton( 'Edit' );
+
+ // Check if focus mode is active
+ await expect( page.locator( 'h1' ) ).toContainText( 'Header' );
+ await expect( page.locator( 'h1' ) ).toContainText( 'Template Part' );
+
+ // Go back
+ await page.getByRole( 'button', { name: 'Back' } ).click();
+
+ // Check that we're editing the template
+ await expect( page.locator( 'h1' ) ).toContainText( 'Blog Home' );
+ await expect( page.locator( 'h1' ) ).toContainText( 'Template' );
+ } );
+} );
From 141e9cd884053db9baec26e43297162841f9e7a7 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Mon, 2 Dec 2024 12:51:58 +0100
Subject: [PATCH 015/600] Site editor: Allow access to quick edit (#67469)
Co-authored-by: youknowriad
Co-authored-by: jsnajdr
---
packages/edit-site/src/components/site-editor-routes/pages.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/edit-site/src/components/site-editor-routes/pages.js b/packages/edit-site/src/components/site-editor-routes/pages.js
index e8c55cd10307e..5f2a4e341e0dc 100644
--- a/packages/edit-site/src/components/site-editor-routes/pages.js
+++ b/packages/edit-site/src/components/site-editor-routes/pages.js
@@ -44,7 +44,7 @@ export const pagesRoute = {
mobile: ,
edit( { query } ) {
const hasQuickEdit =
- ( query.layout ?? 'list' ) === 'list' && !! query.quickEdit;
+ ( query.layout ?? 'list' ) !== 'list' && !! query.quickEdit;
return hasQuickEdit ? (
) : undefined;
@@ -59,7 +59,7 @@ export const pagesRoute = {
},
edit( { query } ) {
const hasQuickEdit =
- ( query.layout ?? 'list' ) === 'list' && !! query.quickEdit;
+ ( query.layout ?? 'list' ) !== 'list' && !! query.quickEdit;
return hasQuickEdit ? 380 : undefined;
},
},
From e07fe5cd9c17702e242df8ab547bddd1c4d79f52 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 2 Dec 2024 12:01:28 +0000
Subject: [PATCH 016/600] Preload: parse post ID from p (path) (#67465)
Co-authored-by: ellatrix
Co-authored-by: youknowriad
---
backport-changelog/6.8/7695.md | 1 +
lib/compat/wordpress-6.8/preload.php | 12 ++++++++++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/backport-changelog/6.8/7695.md b/backport-changelog/6.8/7695.md
index 095c058e6fd10..f45b2039e3027 100644
--- a/backport-changelog/6.8/7695.md
+++ b/backport-changelog/6.8/7695.md
@@ -1,3 +1,4 @@
https://github.com/WordPress/wordpress-develop/pull/7695
* https://github.com/WordPress/gutenberg/pull/66631
+* https://github.com/WordPress/gutenberg/pull/67465
diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php
index 6d92913b41411..494e3ad32ec02 100644
--- a/lib/compat/wordpress-6.8/preload.php
+++ b/lib/compat/wordpress-6.8/preload.php
@@ -10,8 +10,16 @@
*/
function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) {
if ( 'core/edit-site' === $context->name ) {
- if ( ! empty( $_GET['postId'] ) ) {
- $route_for_post = rest_get_route_for_post( $_GET['postId'] );
+ $post_id = null;
+ if ( isset( $_GET['postId'] ) && is_numeric( $_GET['postId'] ) ) {
+ $post_id = (int) $_GET['postId'];
+ }
+ if ( isset( $_GET['p'] ) && preg_match( '/^\/page\/(\d+)$/', $_GET['p'], $matches ) ) {
+ $post_id = (int) $matches[1];
+ }
+
+ if ( $post_id ) {
+ $route_for_post = rest_get_route_for_post( $post_id );
if ( $route_for_post ) {
$paths[] = add_query_arg( 'context', 'edit', $route_for_post );
}
From 39a4d1c93fd3f9bee19db3566e92ce4a03e67544 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Mon, 2 Dec 2024 13:14:14 +0100
Subject: [PATCH 017/600] DataViews: Better handling of missing onClickItem
prop (#67402)
Co-authored-by: youknowriad
Co-authored-by: jsnajdr
---
.../src/components/dataviews-context/index.ts | 5 ++---
.../src/components/dataviews/index.tsx | 5 ++---
.../src/dataviews-layouts/grid/index.tsx | 14 +++++++-------
.../src/dataviews-layouts/table/index.tsx | 16 ++++++++--------
.../utils/get-clickable-item-props.ts | 19 ++++++++++++-------
packages/dataviews/src/types.ts | 2 +-
6 files changed, 32 insertions(+), 29 deletions(-)
diff --git a/packages/dataviews/src/components/dataviews-context/index.ts b/packages/dataviews/src/components/dataviews-context/index.ts
index 19f6b4178b7b5..4bef3ecdbcbb4 100644
--- a/packages/dataviews/src/components/dataviews-context/index.ts
+++ b/packages/dataviews/src/components/dataviews-context/index.ts
@@ -26,7 +26,7 @@ type DataViewsContextType< Item > = {
openedFilter: string | null;
setOpenedFilter: ( openedFilter: string | null ) => void;
getItemId: ( item: Item ) => string;
- onClickItem: ( item: Item ) => void;
+ onClickItem?: ( item: Item ) => void;
isItemClickable: ( item: Item ) => boolean;
};
@@ -44,8 +44,7 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( {
setOpenedFilter: () => {},
openedFilter: null,
getItemId: ( item ) => item.id,
- onClickItem: () => {},
- isItemClickable: () => false,
+ isItemClickable: () => true,
} );
export default DataViewsContext;
diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx
index ee6073f40bf3d..99d9b6d684b08 100644
--- a/packages/dataviews/src/components/dataviews/index.tsx
+++ b/packages/dataviews/src/components/dataviews/index.tsx
@@ -52,8 +52,7 @@ type DataViewsProps< Item > = {
: { getItemId: ( item: Item ) => string } );
const defaultGetItemId = ( item: ItemWithId ) => item.id;
-const defaultIsItemClickable = () => false;
-const defaultOnClickItem = () => {};
+const defaultIsItemClickable = () => true;
const EMPTY_ARRAY: any[] = [];
export default function DataViews< Item >( {
@@ -70,7 +69,7 @@ export default function DataViews< Item >( {
defaultLayouts,
selection: selectionProperty,
onChangeSelection,
- onClickItem = defaultOnClickItem,
+ onClickItem,
isItemClickable = defaultIsItemClickable,
header,
}: DataViewsProps< Item > ) {
diff --git a/packages/dataviews/src/dataviews-layouts/grid/index.tsx b/packages/dataviews/src/dataviews-layouts/grid/index.tsx
index 2a09fb68efab8..17053e01604a5 100644
--- a/packages/dataviews/src/dataviews-layouts/grid/index.tsx
+++ b/packages/dataviews/src/dataviews-layouts/grid/index.tsx
@@ -31,7 +31,7 @@ interface GridItemProps< Item > {
selection: string[];
onChangeSelection: SetSelection;
getItemId: ( item: Item ) => string;
- onClickItem: ( item: Item ) => void;
+ onClickItem?: ( item: Item ) => void;
isItemClickable: ( item: Item ) => boolean;
item: Item;
actions: Action< Item >[];
@@ -66,19 +66,19 @@ function GridItem< Item >( {
) : null;
- const clickableMediaItemProps = getClickableItemProps(
+ const clickableMediaItemProps = getClickableItemProps( {
item,
isItemClickable,
onClickItem,
- 'dataviews-view-grid__media'
- );
+ className: 'dataviews-view-grid__media',
+ } );
- const clickablePrimaryItemProps = getClickableItemProps(
+ const clickablePrimaryItemProps = getClickableItemProps( {
item,
isItemClickable,
onClickItem,
- 'dataviews-view-grid__primary-field'
- );
+ className: 'dataviews-view-grid__primary-field',
+ } );
return (
{
field: NormalizedField< Item >;
item: Item;
isItemClickable: ( item: Item ) => boolean;
- onClickItem: ( item: Item ) => void;
+ onClickItem?: ( item: Item ) => void;
}
interface TableColumnCombinedProps< Item > {
@@ -52,7 +52,7 @@ interface TableColumnCombinedProps< Item > {
item: Item;
view: ViewTableType;
isItemClickable: ( item: Item ) => boolean;
- onClickItem: ( item: Item ) => void;
+ onClickItem?: ( item: Item ) => void;
}
interface TableColumnProps< Item > {
@@ -62,7 +62,7 @@ interface TableColumnProps< Item > {
column: string;
view: ViewTableType;
isItemClickable: ( item: Item ) => boolean;
- onClickItem: ( item: Item ) => void;
+ onClickItem?: ( item: Item ) => void;
}
interface TableRowProps< Item > {
@@ -77,7 +77,7 @@ interface TableRowProps< Item > {
getItemId: ( item: Item ) => string;
onChangeSelection: SetSelection;
isItemClickable: ( item: Item ) => boolean;
- onClickItem: ( item: Item ) => void;
+ onClickItem?: ( item: Item ) => void;
}
function TableColumn< Item >( {
@@ -118,12 +118,12 @@ function TableColumnField< Item >( {
const isItemClickableField = ( i: Item ) =>
isItemClickable( i ) && isPrimaryField;
- const clickableProps = getClickableItemProps(
+ const clickableProps = getClickableItemProps( {
item,
- isItemClickableField,
+ isItemClickable: isItemClickableField,
onClickItem,
- 'dataviews-view-table__cell-content'
- );
+ className: 'dataviews-view-table__cell-content',
+ } );
return (
(
- item: Item,
- isItemClickable: ( item: Item ) => boolean,
- onClickItem: ( item: Item ) => void,
- className: string
-) {
- if ( ! isItemClickable( item ) ) {
+export default function getClickableItemProps< Item >( {
+ item,
+ isItemClickable,
+ onClickItem,
+ className,
+}: {
+ item: Item;
+ isItemClickable: ( item: Item ) => boolean;
+ onClickItem?: ( item: Item ) => void;
+ className: string;
+} ) {
+ if ( ! isItemClickable( item ) || ! onClickItem ) {
return { className };
}
diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts
index 861dc53404f91..0bce8b8cf2c08 100644
--- a/packages/dataviews/src/types.ts
+++ b/packages/dataviews/src/types.ts
@@ -510,7 +510,7 @@ export interface ViewBaseProps< Item > {
onChangeSelection: SetSelection;
selection: string[];
setOpenedFilter: ( fieldId: string ) => void;
- onClickItem: ( item: Item ) => void;
+ onClickItem?: ( item: Item ) => void;
isItemClickable: ( item: Item ) => boolean;
view: View;
}
From 340d617fa5cdab9b9402960de32c6496ef563d1d Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 2 Dec 2024 13:31:54 +0100
Subject: [PATCH 018/600] [mini] drag and drop: fix misplaced drop indicator
(#67434)
Co-authored-by: ellatrix
Co-authored-by: youknowriad
Co-authored-by: Mamaduka
Co-authored-by: ntsekouras
---
.../block-editor/src/components/block-popover/inbetween.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/block-editor/src/components/block-popover/inbetween.js b/packages/block-editor/src/components/block-popover/inbetween.js
index 2ed9ee0bcb284..1d7c176673240 100644
--- a/packages/block-editor/src/components/block-popover/inbetween.js
+++ b/packages/block-editor/src/components/block-popover/inbetween.js
@@ -148,6 +148,10 @@ function BlockPopoverInbetween( {
? nextRect.left - previousRect.right
: 0;
}
+
+ // Avoid a negative width which happens when the next rect
+ // is on the next line.
+ width = Math.max( width, 0 );
}
return new window.DOMRect( left, top, width, height );
From c517e410017f4d65a7c6f03a31c5c2fa15cbbd65 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?=
Date: Mon, 2 Dec 2024 13:32:53 +0100
Subject: [PATCH 019/600] Extensibility: Make Block Bindings work with
`editor.BlockEdit` hook (#67370)
* Block Bindings: Move the place when the attributes get overriden
* Fix failing unit tests
* Wrap Edit with bindings logic only when the block supports it
* Extend the test plugin with `editor.BlockEdit` filter
* Add a new test covering the extensibility inside inspector controls
* Fix the issue with missing context established by sources
Co-authored-by: gziolo
Co-authored-by: SantosGuillamot
---
.../src/components/block-edit/edit.js | 12 +-
.../with-block-bindings-support.js} | 103 ++++--------------
.../block-list/use-block-props/index.js | 2 +-
.../src/components/rich-text/index.js | 2 +-
.../block-editor/src/hooks/block-bindings.js | 10 +-
packages/block-editor/src/hooks/index.js | 1 -
.../block-editor/src/utils/block-bindings.js | 37 +++++++
packages/e2e-tests/plugins/block-bindings.php | 6 +-
.../e2e-tests/plugins/block-bindings/index.js | 45 ++++++++
.../various/block-bindings/post-meta.spec.js | 41 +++++++
10 files changed, 165 insertions(+), 94 deletions(-)
rename packages/block-editor/src/{hooks/use-bindings-attributes.js => components/block-edit/with-block-bindings-support.js} (73%)
diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js
index 83d0e3f406f82..6b1ddd86f4c76 100644
--- a/packages/block-editor/src/components/block-edit/edit.js
+++ b/packages/block-editor/src/components/block-edit/edit.js
@@ -18,6 +18,8 @@ import { useContext, useMemo } from '@wordpress/element';
* Internal dependencies
*/
import BlockContext from '../block-context';
+import { withBlockBindingsSupport } from './with-block-bindings-support';
+import { canBindBlock } from '../../utils/block-bindings';
/**
* Default value used for blocks which do not define their own context needs,
@@ -47,6 +49,8 @@ const Edit = ( props ) => {
const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit );
+const EditWithFiltersAndBindings = withBlockBindingsSupport( EditWithFilters );
+
const EditWithGeneratedProps = ( props ) => {
const { attributes = {}, name } = props;
const blockType = getBlockType( name );
@@ -67,8 +71,12 @@ const EditWithGeneratedProps = ( props ) => {
return null;
}
+ const EditComponent = canBindBlock( name )
+ ? EditWithFiltersAndBindings
+ : EditWithFilters;
+
if ( blockType.apiVersion > 1 ) {
- return ;
+ return ;
}
// Generate a class name for the block's editable form.
@@ -82,7 +90,7 @@ const EditWithGeneratedProps = ( props ) => {
);
return (
- ( props ) => {
const registry = useRegistry();
const blockContext = useContext( BlockContext );
@@ -108,9 +72,9 @@ export const withBlockBindingSupport = createHigherOrderComponent(
() =>
replacePatternOverrideDefaultBindings(
name,
- props.attributes.metadata?.bindings
+ props.attributes?.metadata?.bindings
),
- [ props.attributes.metadata?.bindings, name ]
+ [ props.attributes?.metadata?.bindings, name ]
);
// While this hook doesn't directly call any selectors, `useSelect` is
@@ -196,7 +160,7 @@ export const withBlockBindingSupport = createHigherOrderComponent(
const hasParentPattern = !! updatedContext[ 'pattern/overrides' ];
const hasPatternOverridesDefaultBinding =
- props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
+ props.attributes?.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
?.source === 'core/pattern-overrides';
const _setAttributes = useCallback(
@@ -283,40 +247,13 @@ export const withBlockBindingSupport = createHigherOrderComponent(
);
return (
- <>
-
- >
+
);
},
'withBlockBindingSupport'
);
-
-/**
- * Filters a registered block's settings to enhance a block's `edit` component
- * to upgrade bound attributes.
- *
- * @param {WPBlockSettings} settings - Registered block settings.
- * @param {string} name - Block name.
- * @return {WPBlockSettings} Filtered block settings.
- */
-function shimAttributeSource( settings, name ) {
- if ( ! canBindBlock( name ) ) {
- return settings;
- }
-
- return {
- ...settings,
- edit: withBlockBindingSupport( settings.edit ),
- };
-}
-
-addFilter(
- 'blocks.registerBlockType',
- 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source',
- shimAttributeSource
-);
diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js
index 4696149dc3875..7e50b75e1b956 100644
--- a/packages/block-editor/src/components/block-list/use-block-props/index.js
+++ b/packages/block-editor/src/components/block-list/use-block-props/index.js
@@ -29,7 +29,7 @@ import { useBlockRefProvider } from './use-block-refs';
import { useIntersectionObserver } from './use-intersection-observer';
import { useScrollIntoView } from './use-scroll-into-view';
import { useFlashEditableBlocks } from '../../use-flash-editable-blocks';
-import { canBindBlock } from '../../../hooks/use-bindings-attributes';
+import { canBindBlock } from '../../../utils/block-bindings';
import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility';
/**
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index bc8eca6ea94d0..768ffbb0cdd2d 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -39,7 +39,7 @@ import FormatEdit from './format-edit';
import { getAllowedFormats } from './utils';
import { Content, valueToHTMLString } from './content';
import { withDeprecations } from './with-deprecations';
-import { canBindBlock } from '../../hooks/use-bindings-attributes';
+import { canBindBlock } from '../../utils/block-bindings';
import BlockContext from '../block-context';
export const keyboardShortcutContext = createContext();
diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js
index e10696cc1257d..cec80dffaeaa1 100644
--- a/packages/block-editor/src/hooks/block-bindings.js
+++ b/packages/block-editor/src/hooks/block-bindings.js
@@ -23,15 +23,15 @@ import { useViewportMatch } from '@wordpress/compose';
/**
* Internal dependencies
*/
-import {
- canBindAttribute,
- getBindableAttributes,
-} from '../hooks/use-bindings-attributes';
import { unlock } from '../lock-unlock';
import InspectorControls from '../components/inspector-controls';
import BlockContext from '../components/block-context';
import { useBlockEditContext } from '../components/block-edit';
-import { useBlockBindingsUtils } from '../utils/block-bindings';
+import {
+ canBindAttribute,
+ getBindableAttributes,
+ useBlockBindingsUtils,
+} from '../utils/block-bindings';
import { store as blockEditorStore } from '../store';
const { Menu } = unlock( componentsPrivateApis );
diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js
index 66ff60b691b66..7f9b29376ad1f 100644
--- a/packages/block-editor/src/hooks/index.js
+++ b/packages/block-editor/src/hooks/index.js
@@ -32,7 +32,6 @@ import './metadata';
import blockHooks from './block-hooks';
import blockBindingsPanel from './block-bindings';
import './block-renaming';
-import './use-bindings-attributes';
import './grid-visualizer';
createBlockEditFilter(
diff --git a/packages/block-editor/src/utils/block-bindings.js b/packages/block-editor/src/utils/block-bindings.js
index dcf80d985473b..82f0dff53531a 100644
--- a/packages/block-editor/src/utils/block-bindings.js
+++ b/packages/block-editor/src/utils/block-bindings.js
@@ -13,6 +13,43 @@ function isObjectEmpty( object ) {
return ! object || Object.keys( object ).length === 0;
}
+export const BLOCK_BINDINGS_ALLOWED_BLOCKS = {
+ 'core/paragraph': [ 'content' ],
+ 'core/heading': [ 'content' ],
+ 'core/image': [ 'id', 'url', 'title', 'alt' ],
+ 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ],
+};
+
+/**
+ * Based on the given block name,
+ * check if it is possible to bind the block.
+ *
+ * @param {string} blockName - The block name.
+ * @return {boolean} Whether it is possible to bind the block to sources.
+ */
+export function canBindBlock( blockName ) {
+ return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS;
+}
+
+/**
+ * Based on the given block name and attribute name,
+ * check if it is possible to bind the block attribute.
+ *
+ * @param {string} blockName - The block name.
+ * @param {string} attributeName - The attribute name.
+ * @return {boolean} Whether it is possible to bind the block attribute.
+ */
+export function canBindAttribute( blockName, attributeName ) {
+ return (
+ canBindBlock( blockName ) &&
+ BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName )
+ );
+}
+
+export function getBindableAttributes( blockName ) {
+ return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ];
+}
+
/**
* Contains utils to update the block `bindings` metadata.
*
diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php
index b86673c2c523d..1fd6d8468c77d 100644
--- a/packages/e2e-tests/plugins/block-bindings.php
+++ b/packages/e2e-tests/plugins/block-bindings.php
@@ -41,7 +41,11 @@ function gutenberg_test_block_bindings_registration() {
plugins_url( 'block-bindings/index.js', __FILE__ ),
array(
'wp-blocks',
- 'wp-private-apis',
+ 'wp-block-editor',
+ 'wp-components',
+ 'wp-compose',
+ 'wp-element',
+ 'wp-hooks',
),
filemtime( plugin_dir_path( __FILE__ ) . 'block-bindings/index.js' ),
true
diff --git a/packages/e2e-tests/plugins/block-bindings/index.js b/packages/e2e-tests/plugins/block-bindings/index.js
index 5c364257caed1..63c463e197fa8 100644
--- a/packages/e2e-tests/plugins/block-bindings/index.js
+++ b/packages/e2e-tests/plugins/block-bindings/index.js
@@ -1,4 +1,9 @@
const { registerBlockBindingsSource } = wp.blocks;
+const { InspectorControls } = wp.blockEditor;
+const { PanelBody, TextControl } = wp.components;
+const { createHigherOrderComponent } = wp.compose;
+const { createElement: el, Fragment } = wp.element;
+const { addFilter } = wp.hooks;
const { fieldsList } = window.testingBindings || {};
const getValues = ( { bindings } ) => {
@@ -46,3 +51,43 @@ registerBlockBindingsSource( {
getValues,
canUserEditValue: () => true,
} );
+
+const withBlockBindingsInspectorControl = createHigherOrderComponent(
+ ( BlockEdit ) => {
+ return ( props ) => {
+ if ( ! props.attributes?.metadata?.bindings?.content ) {
+ return el( BlockEdit, props );
+ }
+
+ return el(
+ Fragment,
+ {},
+ el( BlockEdit, props ),
+ el(
+ InspectorControls,
+ {},
+ el(
+ PanelBody,
+ { title: 'Bindings' },
+ el( TextControl, {
+ __next40pxDefaultSize: true,
+ __nextHasNoMarginBottom: true,
+ label: 'Content',
+ value: props.attributes.content,
+ onChange: ( content ) =>
+ props.setAttributes( {
+ content,
+ } ),
+ } )
+ )
+ )
+ );
+ };
+ }
+);
+
+addFilter(
+ 'editor.BlockEdit',
+ 'testing/bindings-inspector-control',
+ withBlockBindingsInspectorControl
+);
diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
index 32334bfc777f2..318707e22f098 100644
--- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
+++ b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
@@ -524,6 +524,47 @@ test.describe( 'Post Meta source', () => {
previewPage.locator( '#connected-paragraph' )
).toHaveText( 'new value' );
} );
+
+ test( 'should be possible to edit the value of the connected custom fields in the inspector control registered by the plugin', async ( {
+ editor,
+ page,
+ } ) => {
+ await editor.insertBlock( {
+ name: 'core/paragraph',
+ attributes: {
+ anchor: 'connected-paragraph',
+ content: 'fallback content',
+ metadata: {
+ bindings: {
+ content: {
+ source: 'core/post-meta',
+ args: {
+ key: 'movie_field',
+ },
+ },
+ },
+ },
+ },
+ } );
+ const contentInput = page.getByRole( 'textbox', {
+ name: 'Content',
+ } );
+ await expect( contentInput ).toHaveValue(
+ 'Movie field default value'
+ );
+ await contentInput.fill( 'new value' );
+ // Check that the paragraph content attribute didn't change.
+ const [ paragraphBlockObject ] = await editor.getBlocks();
+ expect( paragraphBlockObject.attributes.content ).toBe(
+ 'fallback content'
+ );
+ // Check the value of the custom field is being updated by visiting the frontend.
+ const previewPage = await editor.openPreviewPage();
+ await expect(
+ previewPage.locator( '#connected-paragraph' )
+ ).toHaveText( 'new value' );
+ } );
+
test( 'should be possible to connect movie fields through the attributes panel', async ( {
editor,
page,
From d3f344fe6193d7cd86e03e7a00770032e7919ab4 Mon Sep 17 00:00:00 2001
From: Dave Smith
Date: Mon, 2 Dec 2024 13:17:45 +0000
Subject: [PATCH 020/600] Prefer exact matches in Link Search results sorting
(#67367)
* Weight towards exact matches
* Add additional test coverage
Co-authored-by: getdave
Co-authored-by: draganescu
Co-authored-by: talldan
Co-authored-by: jasmussen
Co-authored-by: kevin940726
Co-authored-by: ironprogrammer
Co-authored-by: annezazu
---
.../__experimental-fetch-link-suggestions.ts | 25 +++++++++++--
.../__experimental-fetch-link-suggestions.js | 37 +++++++++++++++++++
2 files changed, 58 insertions(+), 4 deletions(-)
diff --git a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts
index e1a166ee272db..2901219758903 100644
--- a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts
+++ b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts
@@ -270,12 +270,29 @@ export function sortResults( results: SearchResult[], search: string ) {
for ( const result of results ) {
if ( result.title ) {
const titleTokens = tokenize( result.title );
- const matchingTokens = titleTokens.filter( ( titleToken ) =>
- searchTokens.some( ( searchToken ) =>
- titleToken.includes( searchToken )
+ const exactMatchingTokens = titleTokens.filter( ( titleToken ) =>
+ searchTokens.some(
+ ( searchToken ) => titleToken === searchToken
)
);
- scores[ result.id ] = matchingTokens.length / titleTokens.length;
+ const subMatchingTokens = titleTokens.filter( ( titleToken ) =>
+ searchTokens.some(
+ ( searchToken ) =>
+ titleToken !== searchToken &&
+ titleToken.includes( searchToken )
+ )
+ );
+
+ // The score is a combination of exact matches and sub-matches.
+ // More weight is given to exact matches, as they are more relevant (e.g. "cat" vs "caterpillar").
+ // Diving by the total number of tokens in the title normalizes the score and skews
+ // the results towards shorter titles.
+ const exactMatchScore =
+ ( exactMatchingTokens.length / titleTokens.length ) * 10;
+
+ const subMatchScore = subMatchingTokens.length / titleTokens.length;
+
+ scores[ result.id ] = exactMatchScore + subMatchScore;
} else {
scores[ result.id ] = 0;
}
diff --git a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js
index 6878c74332c3d..ad0014ff86ecb 100644
--- a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js
+++ b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js
@@ -393,6 +393,43 @@ describe( 'sortResults', () => {
6,
] );
} );
+
+ it( 'orders results to prefer direct matches over sub matches', () => {
+ const results = [
+ {
+ id: 1,
+ title: 'News',
+ url: 'http://wordpress.local/news/',
+ type: 'page',
+ kind: 'post-type',
+ },
+ {
+ id: 2,
+ title: 'Newspaper',
+ url: 'http://wordpress.local/newspaper/',
+ type: 'page',
+ kind: 'post-type',
+ },
+ {
+ id: 3,
+ title: 'News Flash News',
+ url: 'http://wordpress.local/news-flash-news/',
+ type: 'page',
+ kind: 'post-type',
+ },
+ {
+ id: 4,
+ title: 'News',
+ url: 'http://wordpress.local/news-2/',
+ type: 'page',
+ kind: 'post-type',
+ },
+ ];
+ const order = sortResults( results, 'News' ).map(
+ ( result ) => result.id
+ );
+ expect( order ).toEqual( [ 1, 4, 3, 2 ] );
+ } );
} );
describe( 'tokenize', () => {
From cd26001761b5f93143671ce1505b25732c51692e Mon Sep 17 00:00:00 2001
From: Mitchell Austin
Date: Mon, 2 Dec 2024 05:51:31 -0800
Subject: [PATCH 021/600] =?UTF-8?q?Fix=20Meta=20boxes=20saving=20when=20th?=
=?UTF-8?q?ey=E2=80=99re=20not=20present=20(#67254)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Initialize meta boxes whether or not they’re visible
* Add hook for initialization of meta boxes
* Minimize hook for meta boxes initialization
* Name the export
Co-authored-by: stokesman
Co-authored-by: afercia
Co-authored-by: t-hamano
Co-authored-by: Mamaduka
Co-authored-by: enricobattocchi
---
.../edit-post/src/components/layout/index.js | 13 ++++---
.../src/components/meta-boxes/index.js | 37 ++-----------------
.../meta-boxes/use-meta-box-initialization.js | 32 ++++++++++++++++
3 files changed, 43 insertions(+), 39 deletions(-)
create mode 100644 packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index 5dcbfa2c82cea..b8061571ec66c 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -74,6 +74,7 @@ import useEditPostCommands from '../../commands/use-commands';
import { usePaddingAppender } from './use-padding-appender';
import { useShouldIframe } from './use-should-iframe';
import useNavigateToEntityRecord from '../../hooks/use-navigate-to-entity-record';
+import { useMetaBoxInitialization } from '../meta-boxes/use-meta-box-initialization';
const { getLayoutStyles } = unlock( blockEditorPrivateApis );
const { useCommands } = unlock( coreCommandsPrivateApis );
@@ -413,6 +414,8 @@ function Layout( {
const { isZoomOut } = unlock( select( blockEditorStore ) );
const { getEditorMode, getRenderingMode } = select( editorStore );
const isRenderingPostOnly = getRenderingMode() === 'post-only';
+ const isNotDesignPostType =
+ ! DESIGN_POST_TYPES.includes( currentPostType );
return {
mode: getEditorMode(),
@@ -423,9 +426,7 @@ function Layout( {
!! select( blockEditorStore ).getBlockSelectionStart(),
showIconLabels: get( 'core', 'showIconLabels' ),
isDistractionFree: get( 'core', 'distractionFree' ),
- showMetaBoxes:
- ! DESIGN_POST_TYPES.includes( currentPostType ) &&
- ! isZoomOut(),
+ showMetaBoxes: isNotDesignPostType && ! isZoomOut(),
isWelcomeGuideVisible: isFeatureActive( 'welcomeGuide' ),
templateId:
supportsTemplateMode &&
@@ -435,9 +436,7 @@ function Layout( {
? getTemplateId( currentPostType, currentPostId )
: null,
enablePaddingAppender:
- ! isZoomOut() &&
- isRenderingPostOnly &&
- ! DESIGN_POST_TYPES.includes( currentPostType ),
+ ! isZoomOut() && isRenderingPostOnly && isNotDesignPostType,
};
},
[
@@ -447,6 +446,8 @@ function Layout( {
settings.supportsTemplateMode,
]
);
+ useMetaBoxInitialization( hasActiveMetaboxes );
+
const [ paddingAppenderRef, paddingStyle ] = usePaddingAppender(
enablePaddingAppender
);
diff --git a/packages/edit-post/src/components/meta-boxes/index.js b/packages/edit-post/src/components/meta-boxes/index.js
index 14728c97cf6b6..fdc74a5df4ce9 100644
--- a/packages/edit-post/src/components/meta-boxes/index.js
+++ b/packages/edit-post/src/components/meta-boxes/index.js
@@ -1,9 +1,7 @@
/**
* WordPress dependencies
*/
-import { useSelect, useRegistry } from '@wordpress/data';
-import { useEffect } from '@wordpress/element';
-import { store as editorStore } from '@wordpress/editor';
+import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -13,38 +11,11 @@ import MetaBoxVisibility from './meta-box-visibility';
import { store as editPostStore } from '../../store';
export default function MetaBoxes( { location } ) {
- const registry = useRegistry();
- const { metaBoxes, areMetaBoxesInitialized, isEditorReady } = useSelect(
- ( select ) => {
- const { __unstableIsEditorReady } = select( editorStore );
- const {
- getMetaBoxesPerLocation,
- areMetaBoxesInitialized: _areMetaBoxesInitialized,
- } = select( editPostStore );
- return {
- metaBoxes: getMetaBoxesPerLocation( location ),
- areMetaBoxesInitialized: _areMetaBoxesInitialized(),
- isEditorReady: __unstableIsEditorReady(),
- };
- },
- [ location ]
+ const metaBoxes = useSelect(
+ ( select ) =>
+ select( editPostStore ).getMetaBoxesPerLocation[ location ]
);
- const hasMetaBoxes = !! metaBoxes?.length;
-
- // When editor is ready, initialize postboxes (wp core script) and metabox
- // saving. This initializes all meta box locations, not just this specific
- // one.
- useEffect( () => {
- if ( isEditorReady && hasMetaBoxes && ! areMetaBoxesInitialized ) {
- registry.dispatch( editPostStore ).initializeMetaBoxes();
- }
- }, [ isEditorReady, hasMetaBoxes, areMetaBoxesInitialized ] );
-
- if ( ! areMetaBoxesInitialized ) {
- return null;
- }
-
return (
<>
{ ( metaBoxes ?? [] ).map( ( { id } ) => (
diff --git a/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js b/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js
new file mode 100644
index 0000000000000..4309d85e3c22b
--- /dev/null
+++ b/packages/edit-post/src/components/meta-boxes/use-meta-box-initialization.js
@@ -0,0 +1,32 @@
+/**
+ * WordPress dependencies
+ */
+import { useDispatch, useSelect } from '@wordpress/data';
+import { store as editorStore } from '@wordpress/editor';
+import { useEffect } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { store as editPostStore } from '../../store';
+
+/**
+ * Initializes WordPress `postboxes` script and the logic for saving meta boxes.
+ *
+ * @param { boolean } enabled
+ */
+export const useMetaBoxInitialization = ( enabled ) => {
+ const isEnabledAndEditorReady = useSelect(
+ ( select ) =>
+ enabled && select( editorStore ).__unstableIsEditorReady(),
+ [ enabled ]
+ );
+ const { initializeMetaBoxes } = useDispatch( editPostStore );
+ // The effect has to rerun when the editor is ready because initializeMetaBoxes
+ // will noop until then.
+ useEffect( () => {
+ if ( isEnabledAndEditorReady ) {
+ initializeMetaBoxes();
+ }
+ }, [ isEnabledAndEditorReady, initializeMetaBoxes ] );
+};
From d0c372c881cb7d68bf56703df58f057614817c02 Mon Sep 17 00:00:00 2001
From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com>
Date: Mon, 2 Dec 2024 15:06:19 +0100
Subject: [PATCH 022/600] Fix write mode persisting after disabling the
experiment
Co-authored-by: SantosGuillamot
Co-authored-by: getdave
---
packages/block-editor/src/store/selectors.js | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js
index 75c43770f7e17..dc90f35173252 100644
--- a/packages/block-editor/src/store/selectors.js
+++ b/packages/block-editor/src/store/selectors.js
@@ -3041,9 +3041,7 @@ export const getBlockEditingMode = createRegistrySelector(
clientId = '';
}
- const isNavMode =
- select( preferencesStore )?.get( 'core', 'editorTool' ) ===
- 'navigation';
+ const isNavMode = isNavigationMode( state );
// If the editor is currently not in navigation mode, check if the clientId
// has an editing mode set in the regular derived map.
From 65fa4f3b0273afd7a7b578614a6e9ffd80d05de5 Mon Sep 17 00:00:00 2001
From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com>
Date: Mon, 2 Dec 2024 20:11:50 +0530
Subject: [PATCH 023/600] NumberControl: Deprecate 36px default size (#66730)
* Add the deprecation for 36px default size to number control
* Add the changelog for the deprecation
* Update unit test and supress warning for 40px default size warning from child component
* Add the deperection changelog to unreleased section and updated the component to use __next40pxDefaultSize for NumberControl
* Refactor the test for NumberControl component to reduce changes
* Update box control files to use supress warning prop before default 40px prop
* Supress the console warning for deprecation message from child component
* Addressed the feedbacks on the PR and updated the component based on that
---------
Co-authored-by: hbhalodia
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
.../src/components/line-height-control/index.js | 1 +
packages/components/CHANGELOG.md | 1 +
packages/components/src/angle-picker-control/index.tsx | 2 +-
.../components/src/color-picker/input-with-slider.tsx | 2 +-
packages/components/src/number-control/README.md | 3 ++-
packages/components/src/number-control/index.tsx | 9 +++++++++
.../src/number-control/stories/index.story.tsx | 1 +
packages/components/src/number-control/test/index.tsx | 6 +++++-
packages/components/src/number-control/types.ts | 7 +++++++
packages/components/src/range-control/index.tsx | 1 +
packages/components/src/unit-control/index.tsx | 1 +
11 files changed, 30 insertions(+), 4 deletions(-)
diff --git a/packages/block-editor/src/components/line-height-control/index.js b/packages/block-editor/src/components/line-height-control/index.js
index b2c99c03f8784..e6af602c2875a 100644
--- a/packages/block-editor/src/components/line-height-control/index.js
+++ b/packages/block-editor/src/components/line-height-control/index.js
@@ -93,6 +93,7 @@ const LineHeightControl = ( {
}
spinControls="none"
- size="__unstable-large"
/>
{
return (
{};
@@ -53,9 +54,17 @@ function UnforwardedNumberControl(
size = 'default',
suffix,
onChange = noop,
+ __shouldNotWarnDeprecated36pxSize,
...restProps
} = useDeprecated36pxDefaultSizeProp< NumberControlProps >( props );
+ maybeWarnDeprecated36pxSize( {
+ componentName: 'NumberControl',
+ size,
+ __next40pxDefaultSize: restProps.__next40pxDefaultSize,
+ __shouldNotWarnDeprecated36pxSize,
+ } );
+
if ( hideHTMLArrows ) {
deprecated( 'wp.components.NumberControl hideHTMLArrows prop ', {
alternative: 'spinControls="none"',
diff --git a/packages/components/src/number-control/stories/index.story.tsx b/packages/components/src/number-control/stories/index.story.tsx
index 3feb0d63eadc2..8710839fea6ea 100644
--- a/packages/components/src/number-control/stories/index.story.tsx
+++ b/packages/components/src/number-control/stories/index.story.tsx
@@ -62,4 +62,5 @@ const Template: StoryFn< typeof NumberControl > = ( {
export const Default = Template.bind( {} );
Default.args = {
label: 'Value',
+ __next40pxDefaultSize: true,
};
diff --git a/packages/components/src/number-control/test/index.tsx b/packages/components/src/number-control/test/index.tsx
index 3cf3368f1636b..bf97b520673ea 100644
--- a/packages/components/src/number-control/test/index.tsx
+++ b/packages/components/src/number-control/test/index.tsx
@@ -12,9 +12,13 @@ import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
-import NumberControl from '..';
+import _NumberControl from '..';
import type { NumberControlProps } from '../types';
+const NumberControl = (
+ props: React.ComponentProps< typeof _NumberControl >
+) => <_NumberControl __next40pxDefaultSize { ...props } />;
+
function StatefulNumberControl( props: NumberControlProps ) {
const [ value, setValue ] = useState( props.value );
const handleOnChange = ( v: string | undefined ) => setValue( v );
diff --git a/packages/components/src/number-control/types.ts b/packages/components/src/number-control/types.ts
index 8d198e777bd55..2a0fbf402d356 100644
--- a/packages/components/src/number-control/types.ts
+++ b/packages/components/src/number-control/types.ts
@@ -91,4 +91,11 @@ export type NumberControlProps = Omit<
* The value of the input.
*/
value?: number | string;
+ /**
+ * Do not throw a warning for the deprecated 36px default size.
+ * For internal components of other components that already throw the warning.
+ *
+ * @ignore
+ */
+ __shouldNotWarnDeprecated36pxSize?: boolean;
};
diff --git a/packages/components/src/range-control/index.tsx b/packages/components/src/range-control/index.tsx
index 916571c3ee0e0..89dd8248a1e61 100644
--- a/packages/components/src/range-control/index.tsx
+++ b/packages/components/src/range-control/index.tsx
@@ -350,6 +350,7 @@ function UnforwardedRangeControl(
step={ step }
// @ts-expect-error TODO: Investigate if the `null` value is necessary
value={ inputSliderValue }
+ __shouldNotWarnDeprecated36pxSize
/>
) }
{ allowReset && (
diff --git a/packages/components/src/unit-control/index.tsx b/packages/components/src/unit-control/index.tsx
index 9845c4eb04ef2..65e1e56cda3b3 100644
--- a/packages/components/src/unit-control/index.tsx
+++ b/packages/components/src/unit-control/index.tsx
@@ -224,6 +224,7 @@ function UnforwardedUnitControl(
return (
Date: Mon, 2 Dec 2024 16:07:57 +0100
Subject: [PATCH 024/600] useEditorTitle: fix wrong request without ID (#67475)
Co-authored-by: ellatrix
Co-authored-by: youknowriad
---
.../src/components/editor/use-editor-title.js | 4 ++
test/e2e/specs/site-editor/preload.spec.js | 41 +++++++++++++++++++
2 files changed, 45 insertions(+)
create mode 100644 test/e2e/specs/site-editor/preload.spec.js
diff --git a/packages/edit-site/src/components/editor/use-editor-title.js b/packages/edit-site/src/components/editor/use-editor-title.js
index 727b190117e02..6f0b36f8e3b8b 100644
--- a/packages/edit-site/src/components/editor/use-editor-title.js
+++ b/packages/edit-site/src/components/editor/use-editor-title.js
@@ -22,6 +22,10 @@ function useEditorTitle( postType, postId ) {
const { getEditedEntityRecord, hasFinishedResolution } =
select( coreStore );
+ if ( ! postId ) {
+ return { isLoaded: false };
+ }
+
const _record = getEditedEntityRecord(
'postType',
postType,
diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js
new file mode 100644
index 0000000000000..1e93f783a8a91
--- /dev/null
+++ b/test/e2e/specs/site-editor/preload.spec.js
@@ -0,0 +1,41 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.describe( 'Preload', () => {
+ test.beforeAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'emptytheme' );
+ } );
+
+ test.afterAll( async ( { requestUtils } ) => {
+ await requestUtils.activateTheme( 'twentytwentyone' );
+ } );
+
+ test( 'Should make no requests before the iframe is loaded', async ( {
+ page,
+ admin,
+ } ) => {
+ // Do not use `visitSiteEditor` because it waits for the iframe to load.
+ await admin.visitAdminPage( 'site-editor.php' );
+
+ const requests = [];
+ let isLoaded = false;
+
+ page.on( 'request', ( request ) => {
+ if ( request.resourceType() === 'document' ) {
+ // The iframe also "requests" a blob document. This is the most
+ // reliable way to wait for the iframe to start loading.
+ // `waitForSelector` is always a bit delayed, and we don't want
+ // to detect requests after the iframe mounts.
+ isLoaded = true;
+ } else if ( ! isLoaded && request.resourceType() === 'fetch' ) {
+ requests.push( request.url() );
+ }
+ } );
+
+ await page.waitForFunction( ( _isLoaded ) => _isLoaded, [ isLoaded ] );
+
+ expect( requests ).toEqual( [] );
+ } );
+} );
From 6689c778e6bbb9774dd4487c7f1c29e1ffd207f5 Mon Sep 17 00:00:00 2001
From: Jarda Snajdr
Date: Mon, 2 Dec 2024 17:02:52 +0100
Subject: [PATCH 025/600] SlotFill: remove explicit rerender from portal
version (#67471)
* SlotFill: remove explicit rerender from portal version
* Add changelog entry
Co-authored-by: jsnajdr
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
.../src/slot-fill/bubbles-virtually/fill.tsx | 18 +++++++-----------
.../bubbles-virtually/slot-fill-provider.tsx | 15 ++-------------
.../src/slot-fill/bubbles-virtually/slot.tsx | 11 ++++-------
packages/components/src/slot-fill/types.ts | 7 ++++---
5 files changed, 18 insertions(+), 34 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 748525d6d9c15..94dfd1b3c3811 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -22,6 +22,7 @@
- Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)).
- Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)).
- Exported the `WPCompleter` type as it was being used in block-editor/autocompleters ([#67410](https://github.com/WordPress/gutenberg/pull/67410)).
+- `SlotFill`: remove manual rerenders from the portal `Fill` component ([#67471](https://github.com/WordPress/gutenberg/pull/67471)).
### Bug Fixes
diff --git a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx
index d5287adfab417..ef7bc94ff540b 100644
--- a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx
+++ b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx
@@ -4,7 +4,6 @@
import { useObservableValue } from '@wordpress/compose';
import {
useContext,
- useReducer,
useRef,
useEffect,
createPortal,
@@ -20,18 +19,15 @@ import type { FillComponentProps } from '../types';
export default function Fill( { name, children }: FillComponentProps ) {
const registry = useContext( SlotFillContext );
const slot = useObservableValue( registry.slots, name );
- const [ , rerender ] = useReducer( () => [], [] );
- const ref = useRef( { rerender } );
+ const instanceRef = useRef( {} );
+ // We register fills so we can keep track of their existence.
+ // Slots can use the `useSlotFills` hook to know if there're already fills
+ // registered so they can choose to render themselves or not.
useEffect( () => {
- // We register fills so we can keep track of their existence.
- // Some Slot implementations need to know if there're already fills
- // registered so they can choose to render themselves or not.
- const refValue = ref.current;
- registry.registerFill( name, refValue );
- return () => {
- registry.unregisterFill( name, refValue );
- };
+ const instance = instanceRef.current;
+ registry.registerFill( name, instance );
+ return () => registry.unregisterFill( name, instance );
}, [ registry, name ] );
if ( ! slot || ! slot.ref.current ) {
diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx
index 1dc5ef35ceccf..cf692700eef79 100644
--- a/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx
+++ b/packages/components/src/slot-fill/bubbles-virtually/slot-fill-provider.tsx
@@ -23,13 +23,7 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext {
ref,
fillProps
) => {
- const slot = slots.get( name );
-
- slots.set( name, {
- ...slot,
- ref: ref || slot?.ref,
- fillProps: fillProps || slot?.fillProps || {},
- } );
+ slots.set( name, { ref, fillProps } );
};
const unregisterSlot: SlotFillBubblesVirtuallyContext[ 'unregisterSlot' ] =
@@ -66,12 +60,7 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext {
return;
}
- slot.fillProps = fillProps;
- const slotFills = fills.get( name );
- if ( slotFills ) {
- // Force update fills.
- slotFills.forEach( ( fill ) => fill.rerender() );
- }
+ slots.set( name, { ref, fillProps } );
};
const registerFill: SlotFillBubblesVirtuallyContext[ 'registerFill' ] = (
diff --git a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx
index b8ead7fc7ea8b..e65c055c410a6 100644
--- a/packages/components/src/slot-fill/bubbles-virtually/slot.tsx
+++ b/packages/components/src/slot-fill/bubbles-virtually/slot.tsx
@@ -39,8 +39,7 @@ function Slot(
...restProps
} = props;
- const { registerSlot, unregisterSlot, ...registry } =
- useContext( SlotFillContext );
+ const registry = useContext( SlotFillContext );
const ref = useRef< HTMLElement >( null );
@@ -54,11 +53,9 @@ function Slot(
}, [ fillProps ] );
useLayoutEffect( () => {
- registerSlot( name, ref, fillPropsRef.current );
- return () => {
- unregisterSlot( name, ref );
- };
- }, [ registerSlot, unregisterSlot, name ] );
+ registry.registerSlot( name, ref, fillPropsRef.current );
+ return () => registry.unregisterSlot( name, ref );
+ }, [ registry, name ] );
useLayoutEffect( () => {
registry.updateSlot( name, ref, fillPropsRef.current );
diff --git a/packages/components/src/slot-fill/types.ts b/packages/components/src/slot-fill/types.ts
index 15f082cf3f755..6668057323edd 100644
--- a/packages/components/src/slot-fill/types.ts
+++ b/packages/components/src/slot-fill/types.ts
@@ -110,15 +110,16 @@ export type SlotFillProviderProps = {
export type SlotRef = RefObject< HTMLElement >;
export type Rerenderable = { rerender: () => void };
+export type FillInstance = {};
export type SlotFillBubblesVirtuallyContext = {
slots: ObservableMap< SlotKey, { ref: SlotRef; fillProps: FillProps } >;
- fills: ObservableMap< SlotKey, Rerenderable[] >;
+ fills: ObservableMap< SlotKey, FillInstance[] >;
registerSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void;
unregisterSlot: ( name: SlotKey, ref: SlotRef ) => void;
updateSlot: ( name: SlotKey, ref: SlotRef, fillProps: FillProps ) => void;
- registerFill: ( name: SlotKey, ref: Rerenderable ) => void;
- unregisterFill: ( name: SlotKey, ref: Rerenderable ) => void;
+ registerFill: ( name: SlotKey, instance: FillInstance ) => void;
+ unregisterFill: ( name: SlotKey, instance: FillInstance ) => void;
/**
* This helps the provider know if it's using the default context value or not.
From fa10d2fbd3faea76835374d6c850864d103a98c9 Mon Sep 17 00:00:00 2001
From: Andrea Fercia
Date: Mon, 2 Dec 2024 17:07:55 +0100
Subject: [PATCH 026/600] Fix EntitiesSavedStates panel dialog props. (#67351)
* Fix EntitiesSavedStates panel dialog props.
* Remove renderDialog default value.
Co-authored-by: afercia
Co-authored-by: ntsekouras
Co-authored-by: Mayank-Tripathi32
Co-authored-by: jameskoster
---
.../edit-site/src/components/save-panel/index.js | 12 +++++++++---
packages/editor/README.md | 2 +-
.../src/components/entities-saved-states/index.js | 15 ++++++---------
.../src/components/post-publish-button/index.js | 2 ++
.../src/components/save-publish-panels/index.js | 6 +++++-
5 files changed, 23 insertions(+), 14 deletions(-)
diff --git a/packages/edit-site/src/components/save-panel/index.js b/packages/edit-site/src/components/save-panel/index.js
index 81a0f99557df0..95ec9b9ffc8c4 100644
--- a/packages/edit-site/src/components/save-panel/index.js
+++ b/packages/edit-site/src/components/save-panel/index.js
@@ -31,7 +31,7 @@ const { EntitiesSavedStatesExtensible, NavigableRegion } =
unlock( privateApis );
const { useLocation } = unlock( routerPrivateApis );
-const EntitiesSavedStatesForPreview = ( { onClose } ) => {
+const EntitiesSavedStatesForPreview = ( { onClose, renderDialog } ) => {
const isDirtyProps = useEntitiesSavedStatesIsDirty();
let activateSaveLabel;
if ( isDirtyProps.isDirty ) {
@@ -75,14 +75,20 @@ const EntitiesSavedStatesForPreview = ( { onClose } ) => {
onSave,
saveEnabled: true,
saveLabel: activateSaveLabel,
+ renderDialog,
} }
/>
);
};
-const _EntitiesSavedStates = ( { onClose, renderDialog = undefined } ) => {
+const _EntitiesSavedStates = ( { onClose, renderDialog } ) => {
if ( isPreviewingTheme() ) {
- return ;
+ return (
+
+ );
}
return (
diff --git a/packages/editor/README.md b/packages/editor/README.md
index 8b48d773acb26..dd7b53f421a1d 100644
--- a/packages/editor/README.md
+++ b/packages/editor/README.md
@@ -401,7 +401,7 @@ _Parameters_
- _props_ `Object`: The component props.
- _props.close_ `Function`: The function to close the dialog.
-- _props.renderDialog_ `Function`: The function to render the dialog.
+- _props.renderDialog_ `boolean`: Whether to render the component with modal dialog behavior.
_Returns_
diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js
index ea05bca522941..ad584b0df7557 100644
--- a/packages/editor/src/components/entities-saved-states/index.js
+++ b/packages/editor/src/components/entities-saved-states/index.js
@@ -31,14 +31,11 @@ function identity( values ) {
*
* @param {Object} props The component props.
* @param {Function} props.close The function to close the dialog.
- * @param {Function} props.renderDialog The function to render the dialog.
+ * @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior.
*
* @return {React.ReactNode} The rendered component.
*/
-export default function EntitiesSavedStates( {
- close,
- renderDialog = undefined,
-} ) {
+export default function EntitiesSavedStates( { close, renderDialog } ) {
const isDirtyProps = useIsDirty();
return (
@@ -102,7 +103,10 @@ export default function SavePublishPanels( {
return (
<>
{ isEntitiesSavedStatesOpen && (
-
+
) }
{ ! isEntitiesSavedStatesOpen && unmountableContent }
From 656110814c85d346669dd4fa2c9d3de670d35cbb Mon Sep 17 00:00:00 2001
From: Dave Smith
Date: Mon, 2 Dec 2024 16:39:38 +0000
Subject: [PATCH 027/600] Correctly apply current-menu-ancestor class to
in Nav block #67169
Co-authored-by: getdave
Co-authored-by: MaggieCabrera
Co-authored-by: carolinan
Co-authored-by: mrwweb
Co-authored-by: juanfra
Co-authored-by: draganescu
Co-authored-by: bph
Co-authored-by: jordesign
Co-authored-by: webmandesign
---
packages/block-library/src/navigation-submenu/index.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/block-library/src/navigation-submenu/index.php b/packages/block-library/src/navigation-submenu/index.php
index 92b55e291606e..d61dbb2426c24 100644
--- a/packages/block-library/src/navigation-submenu/index.php
+++ b/packages/block-library/src/navigation-submenu/index.php
@@ -222,7 +222,7 @@ function render_block_core_navigation_submenu( $attributes, $content, $block ) {
if ( strpos( $inner_blocks_html, 'current-menu-item' ) ) {
$tag_processor = new WP_HTML_Tag_Processor( $html );
- while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item__content' ) ) ) {
+ while ( $tag_processor->next_tag( array( 'class_name' => 'wp-block-navigation-item' ) ) ) {
$tag_processor->add_class( 'current-menu-ancestor' );
}
$html = $tag_processor->get_updated_html();
From 1d06b35940ef1d255b08f51cf242ed776d0077b0 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Mon, 2 Dec 2024 17:55:55 +0100
Subject: [PATCH 028/600] Site Editor: Pages: Preload template lookup (#66654)
Co-authored-by: ellatrix
Co-authored-by: Mamaduka
Co-authored-by: jorgefilipecosta
---
backport-changelog/6.8/7695.md | 2 ++
lib/compat/wordpress-6.8/preload.php | 18 +++++++++++++-----
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/backport-changelog/6.8/7695.md b/backport-changelog/6.8/7695.md
index f45b2039e3027..d69a59f2d67d1 100644
--- a/backport-changelog/6.8/7695.md
+++ b/backport-changelog/6.8/7695.md
@@ -2,3 +2,5 @@ https://github.com/WordPress/wordpress-develop/pull/7695
* https://github.com/WordPress/gutenberg/pull/66631
* https://github.com/WordPress/gutenberg/pull/67465
+* https://github.com/WordPress/gutenberg/pull/66579
+* https://github.com/WordPress/gutenberg/pull/66654
diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php
index 494e3ad32ec02..879e263f5a189 100644
--- a/lib/compat/wordpress-6.8/preload.php
+++ b/lib/compat/wordpress-6.8/preload.php
@@ -10,18 +10,26 @@
*/
function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) {
if ( 'core/edit-site' === $context->name ) {
- $post_id = null;
+ $post = null;
if ( isset( $_GET['postId'] ) && is_numeric( $_GET['postId'] ) ) {
- $post_id = (int) $_GET['postId'];
+ $post = get_post( (int) $_GET['postId'] );
}
if ( isset( $_GET['p'] ) && preg_match( '/^\/page\/(\d+)$/', $_GET['p'], $matches ) ) {
- $post_id = (int) $matches[1];
+ $post = get_post( (int) $matches[1] );
}
- if ( $post_id ) {
- $route_for_post = rest_get_route_for_post( $post_id );
+ if ( $post ) {
+ $route_for_post = rest_get_route_for_post( $post );
if ( $route_for_post ) {
$paths[] = add_query_arg( 'context', 'edit', $route_for_post );
+ if ( 'page' === $post->post_type ) {
+ $paths[] = add_query_arg(
+ 'slug',
+ // @see https://github.com/WordPress/gutenberg/blob/489f6067c623926bce7151a76755bb68d8e22ea7/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js#L139-L140
+ 'page-' . $post->post_name,
+ '/wp/v2/templates/lookup'
+ );
+ }
}
}
From d8a457b56b6671fa25dedf2b40841a13bdabe6f1 Mon Sep 17 00:00:00 2001
From: Marin Atanasov <8436925+tyxla@users.noreply.github.com>
Date: Mon, 2 Dec 2024 19:02:59 +0200
Subject: [PATCH 029/600] Block Editor: Animate useScaleCanvas() only when
toggling zoomed out mode (#67481)
Co-authored-by: tyxla
Co-authored-by: ellatrix
---
.../src/components/iframe/use-scale-canvas.js | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/packages/block-editor/src/components/iframe/use-scale-canvas.js b/packages/block-editor/src/components/iframe/use-scale-canvas.js
index c72266e82e2b0..2d8cb217a3255 100644
--- a/packages/block-editor/src/components/iframe/use-scale-canvas.js
+++ b/packages/block-editor/src/components/iframe/use-scale-canvas.js
@@ -2,7 +2,11 @@
* WordPress dependencies
*/
import { useEffect, useRef, useCallback } from '@wordpress/element';
-import { useReducedMotion, useResizeObserver } from '@wordpress/compose';
+import {
+ usePrevious,
+ useReducedMotion,
+ useResizeObserver,
+} from '@wordpress/compose';
/**
* @typedef {Object} TransitionState
@@ -280,6 +284,7 @@ export function useScaleCanvas( {
transitionFromRef.current = transitionToRef.current;
}, [ iframeDocument ] );
+ const previousIsZoomedOut = usePrevious( isZoomedOut );
/**
* Runs when zoom out mode is toggled, and sets the startAnimation flag
* so the animation will start when the next useEffect runs. We _only_
@@ -287,7 +292,7 @@ export function useScaleCanvas( {
* changes due to the container resizing.
*/
useEffect( () => {
- if ( ! iframeDocument ) {
+ if ( ! iframeDocument || previousIsZoomedOut === isZoomedOut ) {
return;
}
@@ -300,7 +305,7 @@ export function useScaleCanvas( {
return () => {
iframeDocument.documentElement.classList.remove( 'is-zoomed-out' );
};
- }, [ iframeDocument, isZoomedOut ] );
+ }, [ iframeDocument, isZoomedOut, previousIsZoomedOut ] );
/**
* This handles:
From 232d14f33f57fcf6f0c07e52bdb5c52ba8f3dcae Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Tue, 3 Dec 2024 02:32:19 +0900
Subject: [PATCH 030/600] DropdownMenu: Increase option height to 40px (#67435)
* DropdownMenu: Increase option height to 40px
* Add changelog
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 3 +++
packages/components/src/dropdown-menu/index.tsx | 1 +
packages/components/src/dropdown-menu/style.scss | 2 +-
packages/components/src/menu-items-choice/style.scss | 1 +
4 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 94dfd1b3c3811..b482a4801c2ea 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -12,6 +12,9 @@
### Enhancements
- `BorderBoxControl`: Reduce gap value when unlinked ([#67049](https://github.com/WordPress/gutenberg/pull/67049)).
+- `DropdownMenu`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)).
+- `MenuItem`: Increase height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)).
+- `MenuItemsChoice`: Increase option height to 40px ([#67435](https://github.com/WordPress/gutenberg/pull/67435)).
### Experimental
diff --git a/packages/components/src/dropdown-menu/index.tsx b/packages/components/src/dropdown-menu/index.tsx
index 0e4501be4839c..de83695978c2d 100644
--- a/packages/components/src/dropdown-menu/index.tsx
+++ b/packages/components/src/dropdown-menu/index.tsx
@@ -164,6 +164,7 @@ function UnconnectedDropdownMenu( dropdownMenuProps: DropdownMenuProps ) {
{ controlSets?.flatMap( ( controlSet, indexOfSet ) =>
controlSet.map( ( control, indexOfControl ) => (
Date: Tue, 3 Dec 2024 02:32:52 +0900
Subject: [PATCH 031/600] CustomSelectControl: Deprecate 36px default size
(#67441)
* CustomSelectControl: Deprecate 36px default size
* Add changelog
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.../font-appearance-control/index.js | 1 +
.../src/components/font-family/index.js | 1 +
packages/components/CHANGELOG.md | 1 +
.../src/custom-select-control/README.md | 2 ++
.../src/custom-select-control/index.tsx | 9 ++++++++
.../stories/index.story.tsx | 1 +
.../src/custom-select-control/test/index.tsx | 22 +++++++++++--------
.../src/custom-select-control/types.ts | 7 ++++++
.../font-size-picker-select.tsx | 1 +
9 files changed, 36 insertions(+), 9 deletions(-)
diff --git a/packages/block-editor/src/components/font-appearance-control/index.js b/packages/block-editor/src/components/font-appearance-control/index.js
index 38cb42e394a3b..f9e8023f93ec6 100644
--- a/packages/block-editor/src/components/font-appearance-control/index.js
+++ b/packages/block-editor/src/components/font-appearance-control/index.js
@@ -153,6 +153,7 @@ export default function FontAppearanceControl( props ) {
{ ...otherProps }
className="components-font-appearance-control"
__next40pxDefaultSize={ __next40pxDefaultSize }
+ __shouldNotWarnDeprecated36pxSize
label={ label }
describedBy={ getDescribedBy() }
options={ selectOptions }
diff --git a/packages/block-editor/src/components/font-family/index.js b/packages/block-editor/src/components/font-family/index.js
index 045d4d5c73ed3..e8d0d7ed2dd80 100644
--- a/packages/block-editor/src/components/font-family/index.js
+++ b/packages/block-editor/src/components/font-family/index.js
@@ -61,6 +61,7 @@ export default function FontFamilyControl( {
return (
onChange( selectedItem.key ) }
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index b482a4801c2ea..f77065cdf7be2 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -6,6 +6,7 @@
- `BoxControl`: Passive deprecate `onMouseOver`/`onMouseOut`. Pass to the `inputProps` prop instead ([#67332](https://github.com/WordPress/gutenberg/pull/67332)).
- `BoxControl`: Deprecate 36px default size ([#66704](https://github.com/WordPress/gutenberg/pull/66704)).
+- `CustomSelectControl`: Deprecate 36px default size ([#67441](https://github.com/WordPress/gutenberg/pull/67441)).
- `NumberControl`: Deprecate 36px default size ([#66730](https://github.com/WordPress/gutenberg/pull/66730)).
- `UnitControl`: Deprecate 36px default size ([#66791](https://github.com/WordPress/gutenberg/pull/66791)).
diff --git a/packages/components/src/custom-select-control/README.md b/packages/components/src/custom-select-control/README.md
index a764a0df133ea..6c175b1fcc5d2 100644
--- a/packages/components/src/custom-select-control/README.md
+++ b/packages/components/src/custom-select-control/README.md
@@ -41,6 +41,7 @@ function MyCustomSelectControl() {
const [ , setFontSize ] = useState();
return (
setFontSize( selectedItem ) }
@@ -52,6 +53,7 @@ function MyControlledCustomSelectControl() {
const [ fontSize, setFontSize ] = useState( options[ 0 ] );
return (
setFontSize( selectedItem ) }
diff --git a/packages/components/src/custom-select-control/index.tsx b/packages/components/src/custom-select-control/index.tsx
index ecd9dc37a8f49..74da8a5c74106 100644
--- a/packages/components/src/custom-select-control/index.tsx
+++ b/packages/components/src/custom-select-control/index.tsx
@@ -18,6 +18,7 @@ import CustomSelectItem from '../custom-select-control-v2/item';
import * as Styled from '../custom-select-control-v2/styles';
import type { CustomSelectProps, CustomSelectOption } from './types';
import { VisuallyHidden } from '../visually-hidden';
+import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size';
function useDeprecatedProps< T extends CustomSelectOption >( {
__experimentalShowSelectedHint,
@@ -56,6 +57,7 @@ function CustomSelectControl< T extends CustomSelectOption >(
) {
const {
__next40pxDefaultSize = false,
+ __shouldNotWarnDeprecated36pxSize,
describedBy,
options,
onChange,
@@ -66,6 +68,13 @@ function CustomSelectControl< T extends CustomSelectOption >(
...restProps
} = useDeprecatedProps( props );
+ maybeWarnDeprecated36pxSize( {
+ componentName: 'CustomSelectControl',
+ __next40pxDefaultSize,
+ size,
+ __shouldNotWarnDeprecated36pxSize,
+ } );
+
const descriptionId = useInstanceId(
CustomSelectControl,
'custom-select-control__description'
diff --git a/packages/components/src/custom-select-control/stories/index.story.tsx b/packages/components/src/custom-select-control/stories/index.story.tsx
index 7c743de58d202..9d430b639547c 100644
--- a/packages/components/src/custom-select-control/stories/index.story.tsx
+++ b/packages/components/src/custom-select-control/stories/index.story.tsx
@@ -63,6 +63,7 @@ const Template: StoryFn< typeof CustomSelectControl > = ( props ) => {
export const Default = Template.bind( {} );
Default.args = {
+ __next40pxDefaultSize: true,
label: 'Label',
options: [
{
diff --git a/packages/components/src/custom-select-control/test/index.tsx b/packages/components/src/custom-select-control/test/index.tsx
index b2ac5c19c6ab3..61d212c26c619 100644
--- a/packages/components/src/custom-select-control/test/index.tsx
+++ b/packages/components/src/custom-select-control/test/index.tsx
@@ -13,7 +13,11 @@ import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
-import UncontrolledCustomSelectControl from '..';
+import _CustomSelectControl from '..';
+
+const UncontrolledCustomSelectControl = (
+ props: React.ComponentProps< typeof _CustomSelectControl >
+) => <_CustomSelectControl __next40pxDefaultSize { ...props } />;
const customClassName = 'amber-skies';
const customStyles = {
@@ -716,7 +720,7 @@ describe( 'Type checking', () => {
const onChange = (): void => {};
- {
onChange={ onChange }
/>;
- {
onChange={ onChange }
/>;
- {
onChange={ onChange }
/>;
- {
}
/>;
- {
onChange={ onChange }
/>;
- {
onChange={ onChange }
/>;
- {
onChange={ onChange }
/>;
- = {
* @default false
*/
__next40pxDefaultSize?: boolean;
+ /**
+ * Do not throw a warning for the deprecated 36px default size.
+ * For internal components of other components that already throw the warning.
+ *
+ * @ignore
+ */
+ __shouldNotWarnDeprecated36pxSize?: boolean;
};
diff --git a/packages/components/src/font-size-picker/font-size-picker-select.tsx b/packages/components/src/font-size-picker/font-size-picker-select.tsx
index 19eaba1cfbecd..b33c382f3393e 100644
--- a/packages/components/src/font-size-picker/font-size-picker-select.tsx
+++ b/packages/components/src/font-size-picker/font-size-picker-select.tsx
@@ -69,6 +69,7 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => {
return (
Date: Tue, 3 Dec 2024 02:44:02 +0900
Subject: [PATCH 032/600] FormFileUpload: Deprecate 36px default size (#67438)
* FormFileUpload: Deprecate 36px default size
* Add changelog
* Tweak descriptions
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
packages/components/CHANGELOG.md | 1 +
.../components/src/form-file-upload/README.md | 106 ++++++++++--------
.../src/form-file-upload/docs-manifest.json | 5 +
.../components/src/form-file-upload/index.tsx | 13 ++-
.../form-file-upload/stories/index.story.tsx | 1 +
.../src/form-file-upload/test/index.tsx | 6 +-
.../components/src/form-file-upload/types.ts | 18 +--
7 files changed, 92 insertions(+), 58 deletions(-)
create mode 100644 packages/components/src/form-file-upload/docs-manifest.json
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index f77065cdf7be2..469d5c9de244e 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -9,6 +9,7 @@
- `CustomSelectControl`: Deprecate 36px default size ([#67441](https://github.com/WordPress/gutenberg/pull/67441)).
- `NumberControl`: Deprecate 36px default size ([#66730](https://github.com/WordPress/gutenberg/pull/66730)).
- `UnitControl`: Deprecate 36px default size ([#66791](https://github.com/WordPress/gutenberg/pull/66791)).
+- `FormFileUpload`: Deprecate 36px default size ([#67438](https://github.com/WordPress/gutenberg/pull/67438)).
### Enhancements
diff --git a/packages/components/src/form-file-upload/README.md b/packages/components/src/form-file-upload/README.md
index 4dd8affc5f54a..c6a7205815de5 100644
--- a/packages/components/src/form-file-upload/README.md
+++ b/packages/components/src/form-file-upload/README.md
@@ -1,95 +1,105 @@
# FormFileUpload
-FormFileUpload is a component that allows users to select files from their local device.
+
-## Usage
+See the WordPress Storybook for more detailed, interactive documentation.
+
+FormFileUpload allows users to select files from their local device.
```jsx
import { FormFileUpload } from '@wordpress/components';
const MyFormFileUpload = () => (
- console.log( event.currentTarget.files ) }
- >
- Upload
-
+ console.log( event.currentTarget.files ) }
+ >
+ Upload
+
);
```
-
## Props
-The component accepts the following props. Props not included in this set will be passed to the `Button` component.
+### `__next40pxDefaultSize`
+
+Start opting into the larger default height that will become the default size in a future version.
+
+ - Type: `boolean`
+ - Required: No
+ - Default: `false`
-### accept
+### `accept`
-A string passed to `input` element that tells the browser which file types can be upload to the upload by the user use. e.g: `image/*,video/*`.
-More information about this string is available in https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers.
+A string passed to the `input` element that tells the browser which
+[file types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers)
+can be uploaded by the user. e.g: `image/*,video/*`.
-- Type: `String`
-- Required: No
+ - Type: `string`
+ - Required: No
-### children
+### `children`
Children are passed as children of `Button`.
-- Type: `Boolean`
-- Required: No
+ - Type: `ReactNode`
+ - Required: No
-### icon
+### `icon`
-The icon to render. Supported values are: Dashicons (specified as strings), functions, Component instances and `null`.
+The icon to render in the default button.
-- Type: `String|Function|Component|null`
-- Required: No
-- Default: `null`
+See the `Icon` component docs for more information.
-### multiple
+ - Type: `IconType`
+ - Required: No
+
+### `multiple`
Whether to allow multiple selection of files or not.
-- Type: `Boolean`
-- Required: No
-- Default: `false`
+ - Type: `boolean`
+ - Required: No
+ - Default: `false`
-### onChange
+### `onChange`
Callback function passed directly to the `input` file element.
Select files will be available in `event.currentTarget.files`.
-- Type: `Function`
-- Required: Yes
+ - Type: `ChangeEventHandler`
+ - Required: Yes
-### onClick
+### `onClick`
Callback function passed directly to the `input` file element.
-This can be useful when you want to force a `change` event to fire when the user chooses the same file again. To do this, set the target value to an empty string in the `onClick` function.
+This can be useful when you want to force a `change` event to fire when
+the user chooses the same file again. To do this, set the target value to
+an empty string in the `onClick` function.
```jsx
( event.target.value = '' ) }
- onChange={ onChange }
+ __next40pxDefaultSize
+ onClick={ ( event ) => ( event.target.value = '' ) }
+ onChange={ onChange }
>
- Upload
+ Upload
```
-- Type: `Function`
-- Required: No
-
-### render
+ - Type: `MouseEventHandler`
+ - Required: No
-Optional callback function used to render the UI. If passed, the component does not render the default UI (a button) and calls this function to render it. The function receives an object with property `openFileDialog`, a function that, when called, opens the browser native file upload modal window.
+### `render`
-- Type: `Function`
-- Required: No
+Optional callback function used to render the UI.
-### __next40pxDefaultSize
-
-Start opting into the larger default height that will become the default size in a future version.
+If passed, the component does not render the default UI (a button) and
+calls this function to render it. The function receives an object with
+property `openFileDialog`, a function that, when called, opens the browser
+native file upload modal window.
-- Type: `Boolean`
-- Required: No
-- Default: `false`
+ - Type: `(arg: { openFileDialog: () => void; }) => ReactNode`
+ - Required: No
diff --git a/packages/components/src/form-file-upload/docs-manifest.json b/packages/components/src/form-file-upload/docs-manifest.json
new file mode 100644
index 0000000000000..cb000536d7356
--- /dev/null
+++ b/packages/components/src/form-file-upload/docs-manifest.json
@@ -0,0 +1,5 @@
+{
+ "$schema": "../../schemas/docs-manifest.json",
+ "displayName": "FormFileUpload",
+ "filePath": "./index.tsx"
+}
diff --git a/packages/components/src/form-file-upload/index.tsx b/packages/components/src/form-file-upload/index.tsx
index 83d563f207476..378dc144c6fe8 100644
--- a/packages/components/src/form-file-upload/index.tsx
+++ b/packages/components/src/form-file-upload/index.tsx
@@ -9,15 +9,17 @@ import { useRef } from '@wordpress/element';
import Button from '../button';
import type { WordPressComponentProps } from '../context';
import type { FormFileUploadProps } from './types';
+import { maybeWarnDeprecated36pxSize } from '../utils/deprecated-36px-size';
/**
- * FormFileUpload is a component that allows users to select files from their local device.
+ * FormFileUpload allows users to select files from their local device.
*
* ```jsx
* import { FormFileUpload } from '@wordpress/components';
*
* const MyFormFileUpload = () => (
* console.log( event.currentTarget.files ) }
* >
@@ -40,6 +42,15 @@ export function FormFileUpload( {
ref.current?.click();
};
+ if ( ! render ) {
+ maybeWarnDeprecated36pxSize( {
+ componentName: 'FormFileUpload',
+ __next40pxDefaultSize: props.__next40pxDefaultSize,
+ // @ts-expect-error - We don't "officially" support all Button props but this likely happens.
+ size: props.size,
+ } );
+ }
+
const ui = render ? (
render( { openFileDialog } )
) : (
diff --git a/packages/components/src/form-file-upload/stories/index.story.tsx b/packages/components/src/form-file-upload/stories/index.story.tsx
index 3599ccc51c22e..176d6b2af6098 100644
--- a/packages/components/src/form-file-upload/stories/index.story.tsx
+++ b/packages/components/src/form-file-upload/stories/index.story.tsx
@@ -36,6 +36,7 @@ const Template: StoryFn< typeof FormFileUpload > = ( props ) => {
export const Default = Template.bind( {} );
Default.args = {
children: 'Select file',
+ __next40pxDefaultSize: true,
};
export const RestrictFileTypes = Template.bind( {} );
diff --git a/packages/components/src/form-file-upload/test/index.tsx b/packages/components/src/form-file-upload/test/index.tsx
index 3035bcaa67064..b82dcd754bcd2 100644
--- a/packages/components/src/form-file-upload/test/index.tsx
+++ b/packages/components/src/form-file-upload/test/index.tsx
@@ -7,13 +7,17 @@ import userEvent from '@testing-library/user-event';
/**
* Internal dependencies
*/
-import FormFileUpload from '..';
+import _FormFileUpload from '..';
/**
* Browser dependencies
*/
const { File } = window;
+const FormFileUpload = (
+ props: React.ComponentProps< typeof _FormFileUpload >
+) => <_FormFileUpload __next40pxDefaultSize { ...props } />;
+
// @testing-library/user-event considers changing to a string as a change, but it do not occur on real browsers, so the comparisons will be against this result
const fakePath = expect.objectContaining( {
target: expect.objectContaining( {
diff --git a/packages/components/src/form-file-upload/types.ts b/packages/components/src/form-file-upload/types.ts
index 728ed959aba76..3bdbbf5ac2d4c 100644
--- a/packages/components/src/form-file-upload/types.ts
+++ b/packages/components/src/form-file-upload/types.ts
@@ -17,10 +17,9 @@ export type FormFileUploadProps = {
*/
__next40pxDefaultSize?: boolean;
/**
- * A string passed to `input` element that tells the browser which file types can be
- * upload to the upload by the user use. e.g: `image/*,video/*`.
- *
- * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers.
+ * A string passed to the `input` element that tells the browser which
+ * [file types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers)
+ * can be uploaded by the user. e.g: `image/*,video/*`.
*/
accept?: InputHTMLAttributes< HTMLInputElement >[ 'accept' ];
/**
@@ -28,7 +27,9 @@ export type FormFileUploadProps = {
*/
children?: ReactNode;
/**
- * The icon to render in the `Button`.
+ * The icon to render in the default button.
+ *
+ * See the `Icon` component docs for more information.
*/
icon?: ComponentProps< typeof Icon >[ 'icon' ];
/**
@@ -50,10 +51,11 @@ export type FormFileUploadProps = {
*
* ```jsx
* ( event.target.value = '' ) }
- * onChange={ onChange }
+ * __next40pxDefaultSize
+ * onClick={ ( event ) => ( event.target.value = '' ) }
+ * onChange={ onChange }
* >
- * Upload
+ * Upload
*
* ```
*/
From fe8e832e425a16df934b29a2e8dc31cc9224ada4 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Tue, 3 Dec 2024 03:09:26 +0900
Subject: [PATCH 033/600] ToolbarButton: Set size to "compact" (#67440)
* ToolbarButton: Set size to "compact"
* Add changelog
* Update snapshots
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.../alignment-control/test/__snapshots__/index.js.snap | 10 +++++-----
.../test/__snapshots__/index.js.snap | 8 ++++----
.../test/__snapshots__/index.js.snap | 6 +++---
packages/components/CHANGELOG.md | 1 +
.../components/src/toolbar/toolbar-button/index.tsx | 2 ++
5 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap
index f2915ead7417b..c1383ae8ecc44 100644
--- a/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap
+++ b/packages/block-editor/src/components/alignment-control/test/__snapshots__/index.js.snap
@@ -12,7 +12,7 @@ exports[`AlignmentUI should allow custom alignment controls to be specified 1`]
align="custom-left"
aria-label="My custom left"
aria-pressed="false"
- class="components-button components-toolbar__control has-icon"
+ class="components-button components-toolbar__control is-compact has-icon"
data-toolbar-item="true"
type="button"
>
@@ -35,7 +35,7 @@ exports[`AlignmentUI should allow custom alignment controls to be specified 1`]
align="custom-right"
aria-label="My custom right"
aria-pressed="true"
- class="components-button components-toolbar__control is-pressed has-icon"
+ class="components-button components-toolbar__control is-compact is-pressed has-icon"
data-toolbar-item="true"
type="button"
>
@@ -100,7 +100,7 @@ exports[`AlignmentUI should match snapshot when controls are visible 1`] = `
align="left"
aria-label="Align text left"
aria-pressed="true"
- class="components-button components-toolbar__control is-pressed has-icon"
+ class="components-button components-toolbar__control is-compact is-pressed has-icon"
data-toolbar-item="true"
type="button"
>
@@ -123,7 +123,7 @@ exports[`AlignmentUI should match snapshot when controls are visible 1`] = `
align="center"
aria-label="Align text center"
aria-pressed="false"
- class="components-button components-toolbar__control has-icon"
+ class="components-button components-toolbar__control is-compact has-icon"
data-toolbar-item="true"
type="button"
>
@@ -146,7 +146,7 @@ exports[`AlignmentUI should match snapshot when controls are visible 1`] = `
align="right"
aria-label="Align text right"
aria-pressed="false"
- class="components-button components-toolbar__control has-icon"
+ class="components-button components-toolbar__control is-compact has-icon"
data-toolbar-item="true"
type="button"
>
diff --git a/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap
index 246e5dca2ae32..9e7e59c0c3144 100644
--- a/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap
+++ b/packages/block-editor/src/components/block-alignment-control/test/__snapshots__/index.js.snap
@@ -42,7 +42,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] =
@@ -64,7 +64,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] =
@@ -86,7 +86,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] =
@@ -108,7 +108,7 @@ exports[`BlockAlignmentUI should match snapshot when controls are visible 1`] =
diff --git a/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap
index e58d9a264310a..e8ad6cddbba56 100644
--- a/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap
+++ b/packages/block-editor/src/components/block-vertical-alignment-control/test/__snapshots__/index.js.snap
@@ -42,7 +42,7 @@ exports[`BlockVerticalAlignmentUI should match snapshot when controls are visibl
@@ -64,7 +64,7 @@ exports[`BlockVerticalAlignmentUI should match snapshot when controls are visibl
@@ -86,7 +86,7 @@ exports[`BlockVerticalAlignmentUI should match snapshot when controls are visibl
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 469d5c9de244e..e53d297fba19a 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -26,6 +26,7 @@
- Upgraded `@ariakit/react` (v0.4.13) and `@ariakit/test` (v0.4.5) ([#65907](https://github.com/WordPress/gutenberg/pull/65907)).
- Upgraded `@ariakit/react` (v0.4.15) and `@ariakit/test` (v0.4.7) ([#67404](https://github.com/WordPress/gutenberg/pull/67404)).
+- `ToolbarButton`: Set size to "compact" ([#67440](https://github.com/WordPress/gutenberg/pull/67440)).
- Exported the `WPCompleter` type as it was being used in block-editor/autocompleters ([#67410](https://github.com/WordPress/gutenberg/pull/67410)).
- `SlotFill`: remove manual rerenders from the portal `Fill` component ([#67471](https://github.com/WordPress/gutenberg/pull/67471)).
diff --git a/packages/components/src/toolbar/toolbar-button/index.tsx b/packages/components/src/toolbar/toolbar-button/index.tsx
index bb591ff7b521c..ae1f54acbfc14 100644
--- a/packages/components/src/toolbar/toolbar-button/index.tsx
+++ b/packages/components/src/toolbar/toolbar-button/index.tsx
@@ -54,6 +54,7 @@ function UnforwardedToolbarButton(
{ ( toolbarItemProps ) => (
Date: Mon, 2 Dec 2024 19:15:40 +0100
Subject: [PATCH 034/600] Zoom out: fix for inserter (#67495)
Makes previousIsZoomedOut a proper ref, and assumes a starting zoom level of 1.
---
.../src/components/iframe/use-scale-canvas.js | 27 ++++++++++---------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/packages/block-editor/src/components/iframe/use-scale-canvas.js b/packages/block-editor/src/components/iframe/use-scale-canvas.js
index 2d8cb217a3255..732fe583d46ac 100644
--- a/packages/block-editor/src/components/iframe/use-scale-canvas.js
+++ b/packages/block-editor/src/components/iframe/use-scale-canvas.js
@@ -2,11 +2,7 @@
* WordPress dependencies
*/
import { useEffect, useRef, useCallback } from '@wordpress/element';
-import {
- usePrevious,
- useReducedMotion,
- useResizeObserver,
-} from '@wordpress/compose';
+import { useReducedMotion, useResizeObserver } from '@wordpress/compose';
/**
* @typedef {Object} TransitionState
@@ -284,7 +280,8 @@ export function useScaleCanvas( {
transitionFromRef.current = transitionToRef.current;
}, [ iframeDocument ] );
- const previousIsZoomedOut = usePrevious( isZoomedOut );
+ const previousIsZoomedOut = useRef( false );
+
/**
* Runs when zoom out mode is toggled, and sets the startAnimation flag
* so the animation will start when the next useEffect runs. We _only_
@@ -292,20 +289,26 @@ export function useScaleCanvas( {
* changes due to the container resizing.
*/
useEffect( () => {
- if ( ! iframeDocument || previousIsZoomedOut === isZoomedOut ) {
- return;
- }
+ const trigger =
+ iframeDocument && previousIsZoomedOut.current !== isZoomedOut;
- if ( isZoomedOut ) {
- iframeDocument.documentElement.classList.add( 'is-zoomed-out' );
+ previousIsZoomedOut.current = isZoomedOut;
+
+ if ( ! trigger ) {
+ return;
}
startAnimationRef.current = true;
+ if ( ! isZoomedOut ) {
+ return;
+ }
+
+ iframeDocument.documentElement.classList.add( 'is-zoomed-out' );
return () => {
iframeDocument.documentElement.classList.remove( 'is-zoomed-out' );
};
- }, [ iframeDocument, isZoomedOut, previousIsZoomedOut ] );
+ }, [ iframeDocument, isZoomedOut ] );
/**
* This handles:
From 92c12fea56c02731c64d6d4a2ed63b9ec72e2100 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Tue, 3 Dec 2024 04:39:30 +0900
Subject: [PATCH 035/600] LetteringSpacingControl: Deprecate 36px default size
(#67429)
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
.../components/letter-spacing-control/README.md | 3 ++-
.../components/letter-spacing-control/index.js | 17 +++++++++++++++++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/packages/block-editor/src/components/letter-spacing-control/README.md b/packages/block-editor/src/components/letter-spacing-control/README.md
index ef119bbc943d2..535ca2ae8cbbf 100644
--- a/packages/block-editor/src/components/letter-spacing-control/README.md
+++ b/packages/block-editor/src/components/letter-spacing-control/README.md
@@ -12,13 +12,14 @@ This component is used for blocks that display text, commonly inside a
Renders a letter spacing control.
```jsx
-import { LetterSpacingControl } from '@wordpress/block-editor';
+import { __experimentalLetterSpacingControl as LetterSpacingControl } from '@wordpress/block-editor';
const MyLetterSpacingControl = () => (
);
```
diff --git a/packages/block-editor/src/components/letter-spacing-control/index.js b/packages/block-editor/src/components/letter-spacing-control/index.js
index 1577e184c4a06..1edbe65a3737e 100644
--- a/packages/block-editor/src/components/letter-spacing-control/index.js
+++ b/packages/block-editor/src/components/letter-spacing-control/index.js
@@ -5,6 +5,7 @@ import {
__experimentalUnitControl as UnitControl,
__experimentalUseCustomUnits as useCustomUnits,
} from '@wordpress/components';
+import deprecated from '@wordpress/deprecated';
import { __ } from '@wordpress/i18n';
/**
@@ -35,9 +36,25 @@ export default function LetterSpacingControl( {
availableUnits: availableUnits || [ 'px', 'em', 'rem' ],
defaultValues: { px: 2, em: 0.2, rem: 0.2 },
} );
+
+ if (
+ ! __next40pxDefaultSize &&
+ ( otherProps.size === undefined || otherProps.size === 'default' )
+ ) {
+ deprecated(
+ `36px default size for wp.blockEditor.__experimentalLetterSpacingControl`,
+ {
+ since: '6.8',
+ version: '7.1',
+ hint: 'Set the `__next40pxDefaultSize` prop to true to start opting into the new default size, which will become the default in a future version.',
+ }
+ );
+ }
+
return (
Date: Mon, 2 Dec 2024 21:06:37 +0100
Subject: [PATCH 036/600] Preload: fix e2e test (#67497)
---
backport-changelog/6.8/7687.md | 1 +
lib/compat/wordpress-6.8/preload.php | 3 ++
test/e2e/specs/site-editor/preload.spec.js | 44 ++++++++++++++--------
3 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/backport-changelog/6.8/7687.md b/backport-changelog/6.8/7687.md
index f1505645df20c..0b5af190964df 100644
--- a/backport-changelog/6.8/7687.md
+++ b/backport-changelog/6.8/7687.md
@@ -1,3 +1,4 @@
https://github.com/WordPress/wordpress-develop/pull/7687
* https://github.com/WordPress/gutenberg/pull/66488
+* https://github.com/WordPress/gutenberg/pull/67497
diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php
index 879e263f5a189..0a36ea7f7227d 100644
--- a/lib/compat/wordpress-6.8/preload.php
+++ b/lib/compat/wordpress-6.8/preload.php
@@ -86,6 +86,9 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) {
*/
$context = current_user_can( 'edit_theme_options' ) ? 'edit' : 'view';
$paths[] = "/wp/v2/global-styles/$global_styles_id?context=$context";
+
+ // Used by getBlockPatternCategories in useBlockEditorSettings.
+ $paths[] = '/wp/v2/block-patterns/categories';
}
return $paths;
}
diff --git a/test/e2e/specs/site-editor/preload.spec.js b/test/e2e/specs/site-editor/preload.spec.js
index 1e93f783a8a91..2cd61283fbd9e 100644
--- a/test/e2e/specs/site-editor/preload.spec.js
+++ b/test/e2e/specs/site-editor/preload.spec.js
@@ -16,26 +16,38 @@ test.describe( 'Preload', () => {
page,
admin,
} ) => {
- // Do not use `visitSiteEditor` because it waits for the iframe to load.
- await admin.visitAdminPage( 'site-editor.php' );
-
const requests = [];
- let isLoaded = false;
- page.on( 'request', ( request ) => {
- if ( request.resourceType() === 'document' ) {
- // The iframe also "requests" a blob document. This is the most
- // reliable way to wait for the iframe to start loading.
- // `waitForSelector` is always a bit delayed, and we don't want
- // to detect requests after the iframe mounts.
- isLoaded = true;
- } else if ( ! isLoaded && request.resourceType() === 'fetch' ) {
- requests.push( request.url() );
+ function onRequest( request ) {
+ if (
+ request.resourceType() === 'document' &&
+ request.url().startsWith( 'blob:' )
+ ) {
+ // Stop recording when the iframe is initialized.
+ page.off( 'request', onRequest );
+ } else if ( request.resourceType() === 'fetch' ) {
+ const url = request.url();
+ const urlObject = new URL( url );
+ const restRoute = urlObject.searchParams.get( 'rest_route' );
+ if ( restRoute ) {
+ requests.push( restRoute );
+ } else {
+ requests.push( url );
+ }
}
- } );
+ }
+
+ page.on( 'request', onRequest );
- await page.waitForFunction( ( _isLoaded ) => _isLoaded, [ isLoaded ] );
+ await admin.visitSiteEditor();
- expect( requests ).toEqual( [] );
+ // To do: these should all be removed or preloaded.
+ expect( requests ).toEqual( [
+ // There are two separate settings OPTIONS requests. We should fix
+ // so the one for canUser and getEntityRecord are reused.
+ '/wp/v2/settings',
+ // Seems to be coming from `enableComplementaryArea`.
+ '/wp/v2/users/me',
+ ] );
} );
} );
From ec756b07e4d66f2ddb3d017b1b09cdd85ee0e48a Mon Sep 17 00:00:00 2001
From: Jerry Jones
Date: Mon, 2 Dec 2024 15:10:48 -0600
Subject: [PATCH 037/600] useScaleCanvas performance improvements (#67496)
* Rely on containerHeight resize listener instead of iframe.documentElement.clientHeight when setting the CSS variable
* Only add CSS variables if we need them (scaleValue < 1)
* Remove unmounting of CSS variables in useEffect return
---------
Co-authored-by: Ella
---
.../src/components/iframe/use-scale-canvas.js | 124 ++++++++----------
1 file changed, 55 insertions(+), 69 deletions(-)
diff --git a/packages/block-editor/src/components/iframe/use-scale-canvas.js b/packages/block-editor/src/components/iframe/use-scale-canvas.js
index 732fe583d46ac..0b2b8d3c137ff 100644
--- a/packages/block-editor/src/components/iframe/use-scale-canvas.js
+++ b/packages/block-editor/src/components/iframe/use-scale-canvas.js
@@ -6,11 +6,11 @@ import { useReducedMotion, useResizeObserver } from '@wordpress/compose';
/**
* @typedef {Object} TransitionState
- * @property {number} scaleValue Scale of the canvas.
- * @property {number} frameSize Size of the frame/offset around the canvas.
- * @property {number} clientHeight ClientHeight of the iframe.
- * @property {number} scrollTop ScrollTop of the iframe.
- * @property {number} scrollHeight ScrollHeight of the iframe.
+ * @property {number} scaleValue Scale of the canvas.
+ * @property {number} frameSize Size of the frame/offset around the canvas.
+ * @property {number} containerHeight containerHeight of the iframe.
+ * @property {number} scrollTop ScrollTop of the iframe.
+ * @property {number} scrollHeight ScrollHeight of the iframe.
*/
/**
@@ -44,27 +44,28 @@ function calculateScale( {
*/
function computeScrollTopNext( transitionFrom, transitionTo ) {
const {
- clientHeight: prevClientHeight,
+ containerHeight: prevContainerHeight,
frameSize: prevFrameSize,
scaleValue: prevScale,
scrollTop,
scrollHeight,
} = transitionFrom;
- const { clientHeight, frameSize, scaleValue } = transitionTo;
+ const { containerHeight, frameSize, scaleValue } = transitionTo;
// Step 0: Start with the current scrollTop.
let scrollTopNext = scrollTop;
// Step 1: Undo the effects of the previous scale and frame around the
// midpoint of the visible area.
scrollTopNext =
- ( scrollTopNext + prevClientHeight / 2 - prevFrameSize ) / prevScale -
- prevClientHeight / 2;
+ ( scrollTopNext + prevContainerHeight / 2 - prevFrameSize ) /
+ prevScale -
+ prevContainerHeight / 2;
// Step 2: Apply the new scale and frame around the midpoint of the
// visible area.
scrollTopNext =
- ( scrollTopNext + clientHeight / 2 ) * scaleValue +
+ ( scrollTopNext + containerHeight / 2 ) * scaleValue +
frameSize -
- clientHeight / 2;
+ containerHeight / 2;
// Step 3: Handle an edge case so that you scroll to the top of the
// iframe if the top of the iframe content is visible in the container.
@@ -78,7 +79,7 @@ function computeScrollTopNext( transitionFrom, transitionTo ) {
const maxScrollTop =
scrollHeight * ( scaleValue / prevScale ) +
frameSize * 2 -
- clientHeight;
+ containerHeight;
// Step 4: Clamp the scrollTopNext between the minimum and maximum
// possible scrollTop positions. Round the value to avoid subpixel
@@ -146,8 +147,10 @@ export function useScaleCanvas( {
} ) {
const [ contentResizeListener, { height: contentHeight } ] =
useResizeObserver();
- const [ containerResizeListener, { width: containerWidth } ] =
- useResizeObserver();
+ const [
+ containerResizeListener,
+ { width: containerWidth, height: containerHeight },
+ ] = useResizeObserver();
const initialContainerWidthRef = useRef( 0 );
const isZoomedOut = scale !== 1;
@@ -186,7 +189,7 @@ export function useScaleCanvas( {
const transitionFromRef = useRef( {
scaleValue,
frameSize,
- clientHeight: 0,
+ containerHeight: 0,
scrollTop: 0,
scrollHeight: 0,
} );
@@ -198,7 +201,7 @@ export function useScaleCanvas( {
const transitionToRef = useRef( {
scaleValue,
frameSize,
- clientHeight: 0,
+ containerHeight: 0,
scrollTop: 0,
scrollHeight: 0,
} );
@@ -333,40 +336,41 @@ export function useScaleCanvas( {
} );
}
- // If we are not going to animate the transition, set the scale and frame size directly.
- // If we are animating, these values will be set when the animation is finished.
- // Example: Opening sidebars that reduce the scale of the canvas, but we don't want to
- // animate the transition.
- if ( ! startAnimationRef.current ) {
+ if ( scaleValue < 1 ) {
+ // If we are not going to animate the transition, set the scale and frame size directly.
+ // If we are animating, these values will be set when the animation is finished.
+ // Example: Opening sidebars that reduce the scale of the canvas, but we don't want to
+ // animate the transition.
+ if ( ! startAnimationRef.current ) {
+ iframeDocument.documentElement.style.setProperty(
+ '--wp-block-editor-iframe-zoom-out-scale',
+ scaleValue
+ );
+ iframeDocument.documentElement.style.setProperty(
+ '--wp-block-editor-iframe-zoom-out-frame-size',
+ `${ frameSize }px`
+ );
+ }
+
iframeDocument.documentElement.style.setProperty(
- '--wp-block-editor-iframe-zoom-out-scale',
- scaleValue
+ '--wp-block-editor-iframe-zoom-out-content-height',
+ `${ contentHeight }px`
);
+
iframeDocument.documentElement.style.setProperty(
- '--wp-block-editor-iframe-zoom-out-frame-size',
- `${ frameSize }px`
+ '--wp-block-editor-iframe-zoom-out-inner-height',
+ `${ containerHeight }px`
);
- }
- iframeDocument.documentElement.style.setProperty(
- '--wp-block-editor-iframe-zoom-out-content-height',
- `${ contentHeight }px`
- );
-
- const clientHeight = iframeDocument.documentElement.clientHeight;
- iframeDocument.documentElement.style.setProperty(
- '--wp-block-editor-iframe-zoom-out-inner-height',
- `${ clientHeight }px`
- );
-
- iframeDocument.documentElement.style.setProperty(
- '--wp-block-editor-iframe-zoom-out-container-width',
- `${ containerWidth }px`
- );
- iframeDocument.documentElement.style.setProperty(
- '--wp-block-editor-iframe-zoom-out-scale-container-width',
- `${ scaleContainerWidth }px`
- );
+ iframeDocument.documentElement.style.setProperty(
+ '--wp-block-editor-iframe-zoom-out-container-width',
+ `${ containerWidth }px`
+ );
+ iframeDocument.documentElement.style.setProperty(
+ '--wp-block-editor-iframe-zoom-out-scale-container-width',
+ `${ scaleContainerWidth }px`
+ );
+ }
/**
* Handle the zoom out animation:
@@ -405,8 +409,9 @@ export function useScaleCanvas( {
// the iframe at this point when we're about to animate the zoom out.
// The iframe scrollTop, scrollHeight, and clientHeight will all be
// the most accurate.
- transitionFromRef.current.clientHeight =
- transitionFromRef.current.clientHeight ?? clientHeight;
+ transitionFromRef.current.containerHeight =
+ transitionFromRef.current.containerHeight ??
+ containerHeight; // Use containerHeight, as it's the previous container height value if none was set.
transitionFromRef.current.scrollTop =
iframeDocument.documentElement.scrollTop;
transitionFromRef.current.scrollHeight =
@@ -415,7 +420,8 @@ export function useScaleCanvas( {
transitionToRef.current = {
scaleValue,
frameSize,
- clientHeight,
+ containerHeight:
+ iframeDocument.documentElement.clientHeight, // use clientHeight to get the actual height of the new container, as it will be the most up-to-date.
};
transitionToRef.current.scrollTop = computeScrollTopNext(
transitionFromRef.current,
@@ -432,27 +438,6 @@ export function useScaleCanvas( {
}
}
}
-
- return () => {
- iframeDocument.documentElement.style.removeProperty(
- '--wp-block-editor-iframe-zoom-out-scale'
- );
- iframeDocument.documentElement.style.removeProperty(
- '--wp-block-editor-iframe-zoom-out-frame-size'
- );
- iframeDocument.documentElement.style.removeProperty(
- '--wp-block-editor-iframe-zoom-out-content-height'
- );
- iframeDocument.documentElement.style.removeProperty(
- '--wp-block-editor-iframe-zoom-out-inner-height'
- );
- iframeDocument.documentElement.style.removeProperty(
- '--wp-block-editor-iframe-zoom-out-container-width'
- );
- iframeDocument.documentElement.style.removeProperty(
- '--wp-block-editor-iframe-zoom-out-scale-container-width'
- );
- };
}, [
startZoomOutAnimation,
finishZoomOutAnimation,
@@ -463,6 +448,7 @@ export function useScaleCanvas( {
iframeDocument,
contentHeight,
containerWidth,
+ containerHeight,
maxContainerWidth,
scaleContainerWidth,
] );
From 88143b3867a04293ac0455c798b8485028a70e11 Mon Sep 17 00:00:00 2001
From: Sunil Prajapati <61308756+akasunil@users.noreply.github.com>
Date: Tue, 3 Dec 2024 02:56:38 +0530
Subject: [PATCH 038/600] Inline Commenting: Added new sidebar as extension of
the canvas (#67347)
Co-authored-by: akasunil
Co-authored-by: ellatrix
---
.../components/collab-sidebar/constants.js | 1 +
.../src/components/collab-sidebar/index.js | 143 ++++++++++++------
.../src/components/collab-sidebar/style.scss | 13 ++
3 files changed, 112 insertions(+), 45 deletions(-)
diff --git a/packages/editor/src/components/collab-sidebar/constants.js b/packages/editor/src/components/collab-sidebar/constants.js
index 748c2afc26374..b62e30c346e1f 100644
--- a/packages/editor/src/components/collab-sidebar/constants.js
+++ b/packages/editor/src/components/collab-sidebar/constants.js
@@ -1 +1,2 @@
+export const collabHistorySidebarName = 'edit-post/collab-history-sidebar';
export const collabSidebarName = 'edit-post/collab-sidebar';
diff --git a/packages/editor/src/components/collab-sidebar/index.js b/packages/editor/src/components/collab-sidebar/index.js
index 17a23a227424a..0fe46c549cff0 100644
--- a/packages/editor/src/components/collab-sidebar/index.js
+++ b/packages/editor/src/components/collab-sidebar/index.js
@@ -2,7 +2,12 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
-import { useSelect, useDispatch, resolveSelect } from '@wordpress/data';
+import {
+ useSelect,
+ useDispatch,
+ resolveSelect,
+ subscribe,
+} from '@wordpress/data';
import { useState, useMemo } from '@wordpress/element';
import { comment as commentIcon } from '@wordpress/icons';
import { addFilter } from '@wordpress/hooks';
@@ -15,12 +20,13 @@ import { store as interfaceStore } from '@wordpress/interface';
* Internal dependencies
*/
import PluginSidebar from '../plugin-sidebar';
-import { collabSidebarName } from './constants';
+import { collabHistorySidebarName, collabSidebarName } from './constants';
import { Comments } from './comments';
import { AddComment } from './add-comment';
import { store as editorStore } from '../../store';
import AddCommentButton from './comment-button';
import AddCommentToolbarButton from './comment-button-toolbar';
+import { useGlobalStylesContext } from '../global-styles-provider';
const isBlockCommentExperimentEnabled =
window?.__experimentalEnableBlockComment;
@@ -44,61 +50,28 @@ addFilter(
modifyBlockCommentAttributes
);
-function CollabSidebarContent( { showCommentBoard, setShowCommentBoard } ) {
+function CollabSidebarContent( {
+ showCommentBoard,
+ setShowCommentBoard,
+ styles,
+ comments,
+} ) {
const { createNotice } = useDispatch( noticesStore );
const { saveEntityRecord, deleteEntityRecord } = useDispatch( coreStore );
const { getEntityRecord } = resolveSelect( coreStore );
- const { postId, threads } = useSelect( ( select ) => {
+ const { postId } = useSelect( ( select ) => {
const { getCurrentPostId } = select( editorStore );
const _postId = getCurrentPostId();
- const data = !! _postId
- ? select( coreStore ).getEntityRecords( 'root', 'comment', {
- post: _postId,
- type: 'block_comment',
- status: 'any',
- per_page: 100,
- } )
- : null;
return {
postId: _postId,
- threads: data,
};
}, [] );
const { getSelectedBlockClientId } = useSelect( blockEditorStore );
const { updateBlockAttributes } = useDispatch( blockEditorStore );
- // Process comments to build the tree structure
- const resultComments = useMemo( () => {
- // Create a compare to store the references to all objects by id
- const compare = {};
- const result = [];
-
- const filteredComments = ( threads ?? [] ).filter(
- ( comment ) => comment.status !== 'trash'
- );
-
- // Initialize each object with an empty `reply` array
- filteredComments.forEach( ( item ) => {
- compare[ item.id ] = { ...item, reply: [] };
- } );
-
- // Iterate over the data to build the tree structure
- filteredComments.forEach( ( item ) => {
- if ( item.parent === 0 ) {
- // If parent is 0, it's a root item, push it to the result array
- result.push( compare[ item.id ] );
- } else if ( compare[ item.parent ] ) {
- // Otherwise, find its parent and push it to the parent's `reply` array
- compare[ item.parent ].reply.push( compare[ item.id ] );
- }
- } );
-
- return result;
- }, [ threads ] );
-
// Function to save the comment.
const addNewComment = async ( comment, parentCommentId ) => {
const args = {
@@ -222,14 +195,14 @@ function CollabSidebarContent( { showCommentBoard, setShowCommentBoard } ) {
};
return (
-
+
{
return {
@@ -270,6 +244,68 @@ export default function CollabSidebar() {
enableComplementaryArea( 'core', 'edit-post/collab-sidebar' );
};
+ const { threads } = useSelect( ( select ) => {
+ const { getCurrentPostId } = select( editorStore );
+ const _postId = getCurrentPostId();
+ const data = !! _postId
+ ? select( coreStore ).getEntityRecords( 'root', 'comment', {
+ post: _postId,
+ type: 'block_comment',
+ status: 'any',
+ per_page: 100,
+ } )
+ : null;
+
+ return {
+ postId: _postId,
+ threads: data,
+ };
+ }, [] );
+
+ // Process comments to build the tree structure
+ const resultComments = useMemo( () => {
+ // Create a compare to store the references to all objects by id
+ const compare = {};
+ const result = [];
+
+ const filteredComments = ( threads ?? [] ).filter(
+ ( comment ) => comment.status !== 'trash'
+ );
+
+ // Initialize each object with an empty `reply` array
+ filteredComments.forEach( ( item ) => {
+ compare[ item.id ] = { ...item, reply: [] };
+ } );
+
+ // Iterate over the data to build the tree structure
+ filteredComments.forEach( ( item ) => {
+ if ( item.parent === 0 ) {
+ // If parent is 0, it's a root item, push it to the result array
+ result.push( compare[ item.id ] );
+ } else if ( compare[ item.parent ] ) {
+ // Otherwise, find its parent and push it to the parent's `reply` array
+ compare[ item.parent ].reply.push( compare[ item.id ] );
+ }
+ } );
+
+ return result;
+ }, [ threads ] );
+
+ // Get the global styles to set the background color of the sidebar.
+ const { merged: GlobalStyles } = useGlobalStylesContext();
+ const backgroundColor = GlobalStyles?.styles?.color?.background;
+
+ if ( 0 < resultComments.length ) {
+ const unsubscribe = subscribe( () => {
+ const activeSidebar = getActiveComplementaryArea( 'core' );
+
+ if ( ! activeSidebar ) {
+ enableComplementaryArea( 'core', collabSidebarName );
+ unsubscribe();
+ }
+ } );
+ }
+
// Check if the experimental flag is enabled.
if ( ! isBlockCommentExperimentEnabled || postStatus === 'publish' ) {
return null; // or maybe return some message indicating no threads are available.
@@ -283,14 +319,31 @@ export default function CollabSidebar() {
<>
+
+
+
>
diff --git a/packages/editor/src/components/collab-sidebar/style.scss b/packages/editor/src/components/collab-sidebar/style.scss
index 2f937e3df9a25..2c1426f1dd75d 100644
--- a/packages/editor/src/components/collab-sidebar/style.scss
+++ b/packages/editor/src/components/collab-sidebar/style.scss
@@ -1,5 +1,18 @@
+.interface-interface-skeleton__sidebar:has(.editor-collab-sidebar) {
+ box-shadow: none;
+
+ .interface-complementary-area-header {
+ display: none;
+ }
+}
+
+.editor-collab-sidebar {
+ height: 100%;
+}
+
.editor-collab-sidebar-panel {
padding: $grid-unit-20;
+ height: 100%;
&__thread {
position: relative;
From 66d952b8905209fa2ed935fe55e3744957651983 Mon Sep 17 00:00:00 2001
From: Lena Morita
Date: Tue, 3 Dec 2024 07:55:29 +0900
Subject: [PATCH 039/600] Storybook: Support keyword search in Icon Library
(#67442)
Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla
---
packages/icons/src/icon/stories/index.story.js | 18 ++++++++++++++----
packages/icons/src/icon/stories/keywords.ts | 13 +++++++++++++
2 files changed, 27 insertions(+), 4 deletions(-)
create mode 100644 packages/icons/src/icon/stories/keywords.ts
diff --git a/packages/icons/src/icon/stories/index.story.js b/packages/icons/src/icon/stories/index.story.js
index 092434de43b4d..8cbf65d9f259e 100644
--- a/packages/icons/src/icon/stories/index.story.js
+++ b/packages/icons/src/icon/stories/index.story.js
@@ -9,6 +9,7 @@ import { useState, Fragment } from '@wordpress/element';
import Icon from '../';
import check from '../../library/check';
import * as icons from '../../';
+import keywords from './keywords';
const { Icon: _Icon, ...availableIcons } = icons;
@@ -46,14 +47,23 @@ const LibraryExample = () => {
const [ filter, setFilter ] = useState( '' );
const filteredIcons = filter.length
? Object.fromEntries(
- Object.entries( availableIcons ).filter( ( [ name ] ) =>
- name.toLowerCase().includes( filter.toLowerCase() )
- )
+ Object.entries( availableIcons ).filter( ( [ name ] ) => {
+ const normalizedName = name.toLowerCase();
+ const normalizedFilter = filter.toLowerCase();
+
+ return (
+ normalizedName.includes( normalizedFilter ) ||
+ // @ts-expect-error - Not worth the effort to cast `name`
+ keywords[ name ]?.some( ( keyword ) =>
+ keyword.toLowerCase().includes( normalizedFilter )
+ )
+ );
+ } )
)
: availableIcons;
return (
-
+
Filter Icons
> = {
+ cancelCircleFilled: [ 'close' ],
+ create: [ 'add' ],
+ file: [ 'folder' ],
+ seen: [ 'show' ],
+ thumbsDown: [ 'dislike' ],
+ thumbsUp: [ 'like' ],
+ trash: [ 'delete' ],
+ unseen: [ 'hide' ],
+ warning: [ 'alert', 'caution' ],
+};
+
+export default keywords;
From 6ef2f24d316a4368f0d21a97ce76dc905d345d85 Mon Sep 17 00:00:00 2001
From: wwdes <55145125+wwdes@users.noreply.github.com>
Date: Tue, 3 Dec 2024 00:08:03 +0100
Subject: [PATCH 040/600] Added disableAlpha prop to CustomGradientPicker and
GradientPicker components (#66974)
* add enableAlpha prop to GradientPicker and custom-gradient-picker
* Update packages/components/CHANGELOG.md
Co-authored-by: Lena Morita
* Update packages/components/src/custom-gradient-picker/types.ts
Co-authored-by: Lena Morita
* Update packages/components/src/gradient-picker/types.ts
Co-authored-by: Lena Morita
* packages/components/src/gradient-picker/README.md update
---------
Unlinked contributors: wwdes.
Co-authored-by: tyxla
Co-authored-by: mirka <0mirka00@git.wordpress.org>
---
packages/components/CHANGELOG.md | 5 +++++
packages/components/src/custom-gradient-picker/index.tsx | 2 ++
packages/components/src/custom-gradient-picker/types.ts | 6 ++++++
packages/components/src/gradient-picker/README.md | 8 ++++++++
packages/components/src/gradient-picker/index.tsx | 2 ++
packages/components/src/gradient-picker/types.ts | 6 ++++++
6 files changed, 29 insertions(+)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index e53d297fba19a..e35ba460ef7c5 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,11 @@
## Unreleased
+### Enhancements
+
+- `GradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974))
+- `CustomGradientPicker`: Add `enableAlpha` prop ([#66974](https://github.com/WordPress/gutenberg/pull/66974))
+
### Deprecations
- `BoxControl`: Passive deprecate `onMouseOver`/`onMouseOut`. Pass to the `inputProps` prop instead ([#67332](https://github.com/WordPress/gutenberg/pull/67332)).
diff --git a/packages/components/src/custom-gradient-picker/index.tsx b/packages/components/src/custom-gradient-picker/index.tsx
index dd0659515234a..8d53cd9f3d0ea 100644
--- a/packages/components/src/custom-gradient-picker/index.tsx
+++ b/packages/components/src/custom-gradient-picker/index.tsx
@@ -140,6 +140,7 @@ const GradientTypePicker = ( {
export function CustomGradientPicker( {
value,
onChange,
+ enableAlpha = true,
__experimentalIsRenderedInSidebar = false,
}: CustomGradientPickerProps ) {
const { gradientAST, hasGradient } = getGradientAstWithDefault( value );
@@ -167,6 +168,7 @@ export function CustomGradientPicker( {
__experimentalIsRenderedInSidebar={
__experimentalIsRenderedInSidebar
}
+ disableAlpha={ ! enableAlpha }
background={ background }
hasGradient={ hasGradient }
value={ controlPoints }
diff --git a/packages/components/src/custom-gradient-picker/types.ts b/packages/components/src/custom-gradient-picker/types.ts
index f9efb90799daf..17702c74ef527 100644
--- a/packages/components/src/custom-gradient-picker/types.ts
+++ b/packages/components/src/custom-gradient-picker/types.ts
@@ -26,6 +26,12 @@ export type CustomGradientPickerProps = {
* the `currentGradient` as an argument.
*/
onChange: ( currentGradient: string ) => void;
+ /**
+ * Whether to enable alpha transparency options in the picker.
+ *
+ * @default true
+ */
+ enableAlpha?: boolean;
/**
* Whether this is rendered in the sidebar.
*
diff --git a/packages/components/src/gradient-picker/README.md b/packages/components/src/gradient-picker/README.md
index a8ee9d990c5c2..ec0210d03c0a4 100644
--- a/packages/components/src/gradient-picker/README.md
+++ b/packages/components/src/gradient-picker/README.md
@@ -100,6 +100,14 @@ gradients from `gradients` will be shown.
- Required: No
- Default: `false`
+### `enableAlpha`
+
+Whether to enable alpha transparency options in the picker.
+
+ - Type: `boolean`
+ - Required: No
+ - Default: `true`
+
### `gradients`
An array of objects as predefined gradients displayed above the gradient
diff --git a/packages/components/src/gradient-picker/index.tsx b/packages/components/src/gradient-picker/index.tsx
index 124a89c7e016e..28491d8a56010 100644
--- a/packages/components/src/gradient-picker/index.tsx
+++ b/packages/components/src/gradient-picker/index.tsx
@@ -213,6 +213,7 @@ export function GradientPicker( {
onChange,
value,
clearable = true,
+ enableAlpha = true,
disableCustomGradients = false,
__experimentalIsRenderedInSidebar,
headingLevel = 2,
@@ -230,6 +231,7 @@ export function GradientPicker( {
__experimentalIsRenderedInSidebar={
__experimentalIsRenderedInSidebar
}
+ enableAlpha={ enableAlpha }
value={ value }
onChange={ onChange }
/>
diff --git a/packages/components/src/gradient-picker/types.ts b/packages/components/src/gradient-picker/types.ts
index 8ac2c6de9f2cf..3497dd8c5ac00 100644
--- a/packages/components/src/gradient-picker/types.ts
+++ b/packages/components/src/gradient-picker/types.ts
@@ -56,6 +56,12 @@ type GradientPickerBaseProps = {
* @default true
*/
loop?: boolean;
+ /**
+ * Whether to enable alpha transparency options in the picker.
+ *
+ * @default true
+ */
+ enableAlpha?: boolean;
} & (
| {
// TODO: [#54055] Either this or `aria-labelledby` should be required
From 680fef531408601dcd04e199aefdfac7190cb577 Mon Sep 17 00:00:00 2001
From: Pawan Kumar
Date: Tue, 3 Dec 2024 11:33:01 +0530
Subject: [PATCH 041/600] Navigation Block: Fix issue with double-clicking
"Create a new menu" causing duplicate menus. (#67488)
Co-authored-by: creador-dev
Co-authored-by: talldan
---
.../navigation/edit/deleted-navigation-warning.js | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js
index 22d1e339c5c00..3d40f4d031ed3 100644
--- a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js
+++ b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js
@@ -4,9 +4,16 @@
import { Warning } from '@wordpress/block-editor';
import { Button, Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { createInterpolateElement } from '@wordpress/element';
+import { useState, createInterpolateElement } from '@wordpress/element';
function DeletedNavigationWarning( { onCreateNew, isNotice = false } ) {
+ const [ isButtonDisabled, setIsButtonDisabled ] = useState( false );
+
+ const handleButtonClick = () => {
+ setIsButtonDisabled( true );
+ onCreateNew();
+ };
+
const message = createInterpolateElement(
__(
'Navigation Menu has been deleted or is unavailable. Create a new Menu? '
@@ -15,8 +22,10 @@ function DeletedNavigationWarning( { onCreateNew, isNotice = false } ) {
button: (
),
}
From 9addc70d0c97730253a5d2afbce8528140a2a431 Mon Sep 17 00:00:00 2001
From: Mayank Tripathi <70465598+Mayank-Tripathi32@users.noreply.github.com>
Date: Tue, 3 Dec 2024 13:27:07 +0530
Subject: [PATCH 042/600] Fix editor crash due to homepage action (#67500)
Co-authored-by: Mayank-Tripathi32
Co-authored-by: t-hamano
Co-authored-by: oandregal
---
packages/editor/src/components/post-actions/actions.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js
index 1b6ff4fbe384b..808134ea969a1 100644
--- a/packages/editor/src/components/post-actions/actions.js
+++ b/packages/editor/src/components/post-actions/actions.js
@@ -52,10 +52,11 @@ export function usePostActions( { postType, onActionPerformed, context } ) {
}, [ registerPostTypeSchema, postType ] );
return useMemo( () => {
- let actions = [
- ...defaultActions,
- shouldShowSetAsHomepageAction ? setAsHomepageAction : [],
- ];
+ let actions = [ ...defaultActions ];
+ if ( shouldShowSetAsHomepageAction ) {
+ actions.push( setAsHomepageAction );
+ }
+
// Filter actions based on provided context. If not provided
// all actions are returned. We'll have a single entry for getting the actions
// and the consumer should provide the context to filter the actions, if needed.
From df9a213d9c8b7a92785ab82a2d504aa54a369101 Mon Sep 17 00:00:00 2001
From: Riad Benguella
Date: Tue, 3 Dec 2024 10:09:03 +0100
Subject: [PATCH 043/600] Site Editor: Fix the patterns route on mobile
(#67467)
Co-authored-by: youknowriad
Co-authored-by: tyxla
Co-authored-by: t-hamano
Co-authored-by: ramonjd
---
.../edit-site/src/components/site-editor-routes/patterns.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/edit-site/src/components/site-editor-routes/patterns.js b/packages/edit-site/src/components/site-editor-routes/patterns.js
index 48207cfe1c1d2..db97c4b5c080f 100644
--- a/packages/edit-site/src/components/site-editor-routes/patterns.js
+++ b/packages/edit-site/src/components/site-editor-routes/patterns.js
@@ -10,6 +10,6 @@ export const patternsRoute = {
areas: {
sidebar: ,
content: ,
- mobile: ,
+ mobile: ,
},
};
From 826c43043b5b02a6049c50d94cafee0a0af09c5b Mon Sep 17 00:00:00 2001
From: Sagar Prajapati
Date: Tue, 3 Dec 2024 14:54:46 +0530
Subject: [PATCH 044/600] Align Submenu block and Nav Link block by including
description and wrapping span (#67198)
* Fix missing wp-block-navigation-item__label span
* Addressed feedback
* Updated README.md
Co-authored-by: prajapatisagar
Co-authored-by: MaggieCabrera
Co-authored-by: getdave
Co-authored-by: carolinan
---
.../block-library/src/navigation-submenu/edit.js | 5 +++++
.../src/navigation-submenu/index.php | 16 ++++++++++++++++
packages/block-library/src/navigation/README.md | 1 +
3 files changed, 22 insertions(+)
diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js
index acc9510d0d3d3..261b2ad8502fb 100644
--- a/packages/block-library/src/navigation-submenu/edit.js
+++ b/packages/block-library/src/navigation-submenu/edit.js
@@ -474,6 +474,11 @@ export default function NavigationSubmenuEdit( {
}
} }
/>
+ { description && (
+
+ { description }
+
+ ) }
{ ! openSubmenusOnClick && isLinkOpen && (
';
diff --git a/packages/block-library/src/navigation/README.md b/packages/block-library/src/navigation/README.md
index b59c4b40d85e7..cbd52a8f21f0e 100644
--- a/packages/block-library/src/navigation/README.md
+++ b/packages/block-library/src/navigation/README.md
@@ -10,4 +10,5 @@ The structural CSS for the navigation block targets generic classnames across me
- `.wp-block-navigation-item` is applied to every menu item.
- `.wp-block-navigation-item__content` is applied to the link inside a menu item.
- `.wp-block-navigation-item__label` is applied to the innermost container around the menu item text label.
+- `.wp-block-navigation-item__description` is applied to the innermost container around the menu item description.
- `.wp-block-navigation__submenu-icon` is applied to the submenu indicator (chevron).
From 5900cf69ec81c3fa7c446499769929eda0a7b3ce Mon Sep 17 00:00:00 2001
From: Luigi Teschio
Date: Tue, 3 Dec 2024 10:48:09 +0100
Subject: [PATCH 045/600] Move `duplicateTemplatePart` action to the
`@wordpress/fields` package (#65390)
Co-authored-by: gigitux
Co-authored-by: youknowriad
Co-authored-by: oandregal
---
packages/base-styles/_z-index.scss | 2 +-
.../post-actions/set-as-homepage.js | 17 ++-
.../convert-to-template-part.js | 2 +-
.../editor/src/dataviews/actions/utils.ts | 64 ---------
.../src/dataviews/store/private-actions.ts | 4 +-
packages/editor/src/dataviews/types.ts | 88 ------------
packages/editor/src/private-apis.js | 2 +-
packages/editor/src/style.scss | 1 -
packages/fields/README.md | 25 ++++
.../src}/actions/duplicate-template-part.tsx | 12 +-
packages/fields/src/actions/index.ts | 1 +
packages/fields/src/actions/rename-post.tsx | 9 +-
packages/fields/src/actions/reset-post.tsx | 13 +-
packages/fields/src/actions/utils.ts | 18 +--
.../create-template-part-modal/index.tsx} | 130 +++++++++++++-----
.../create-template-part-modal/style.scss | 14 +-
.../create-template-part-modal/test/utils.js | 0
.../create-template-part-modal/utils.js | 23 ++--
packages/fields/src/index.ts | 3 +-
packages/fields/src/style.scss | 1 +
packages/fields/src/types.ts | 3 +
packages/fields/tsconfig.json | 1 +
22 files changed, 185 insertions(+), 248 deletions(-)
delete mode 100644 packages/editor/src/dataviews/actions/utils.ts
delete mode 100644 packages/editor/src/dataviews/types.ts
rename packages/{editor/src/dataviews => fields/src}/actions/duplicate-template-part.tsx (87%)
rename packages/{editor/src/components/create-template-part-modal/index.js => fields/src/components/create-template-part-modal/index.tsx} (52%)
rename packages/{editor => fields}/src/components/create-template-part-modal/style.scss (69%)
rename packages/{editor => fields}/src/components/create-template-part-modal/test/utils.js (100%)
rename packages/{editor => fields}/src/components/create-template-part-modal/utils.js (86%)
diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss
index 167af583ed9dd..c2ee8f698c2c8 100644
--- a/packages/base-styles/_z-index.scss
+++ b/packages/base-styles/_z-index.scss
@@ -123,7 +123,7 @@ $z-layers: (
// Should be above the popover (dropdown)
".reusable-blocks-menu-items__convert-modal": 1000001,
".patterns-menu-items__convert-modal": 1000001,
- ".editor-create-template-part-modal": 1000001,
+ ".fields-create-template-part-modal": 1000001,
".block-editor-block-lock-modal": 1000001,
".block-editor-template-part__selection-modal": 1000001,
".block-editor-block-rename-modal": 1000001,
diff --git a/packages/editor/src/components/post-actions/set-as-homepage.js b/packages/editor/src/components/post-actions/set-as-homepage.js
index 0366a52482f2a..70bdeeeefe70f 100644
--- a/packages/editor/src/components/post-actions/set-as-homepage.js
+++ b/packages/editor/src/components/post-actions/set-as-homepage.js
@@ -12,11 +12,20 @@ import {
import { useDispatch, useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';
+import { decodeEntities } from '@wordpress/html-entities';
-/**
- * Internal dependencies
- */
-import { getItemTitle } from '../../dataviews/actions/utils';
+const getItemTitle = ( item ) => {
+ if ( typeof item.title === 'string' ) {
+ return decodeEntities( item.title );
+ }
+ if ( item.title && 'rendered' in item.title ) {
+ return decodeEntities( item.title.rendered );
+ }
+ if ( item.title && 'raw' in item.title ) {
+ return decodeEntities( item.title.raw );
+ }
+ return '';
+};
const SetAsHomepageModal = ( { items, closeModal } ) => {
const [ item ] = items;
diff --git a/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js b/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js
index 4ae15d1dd178c..9ce11772a34b7 100644
--- a/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js
+++ b/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js
@@ -13,7 +13,7 @@ import { symbolFilled } from '@wordpress/icons';
/**
* Internal dependencies
*/
-import CreateTemplatePartModal from '../create-template-part-modal';
+import { CreateTemplatePartModal } from '@wordpress/fields';
export default function ConvertToTemplatePart( { clientIds, blocks } ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
diff --git a/packages/editor/src/dataviews/actions/utils.ts b/packages/editor/src/dataviews/actions/utils.ts
deleted file mode 100644
index 33a2be16397f3..0000000000000
--- a/packages/editor/src/dataviews/actions/utils.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { decodeEntities } from '@wordpress/html-entities';
-
-/**
- * Internal dependencies
- */
-import {
- TEMPLATE_ORIGINS,
- TEMPLATE_PART_POST_TYPE,
- TEMPLATE_POST_TYPE,
-} from '../../store/constants';
-
-import type { Post, TemplatePart, Template } from '../types';
-
-export function isTemplate( post: Post ): post is Template {
- return post.type === TEMPLATE_POST_TYPE;
-}
-
-export function isTemplatePart( post: Post ): post is TemplatePart {
- return post.type === TEMPLATE_PART_POST_TYPE;
-}
-
-export function isTemplateOrTemplatePart(
- p: Post
-): p is Template | TemplatePart {
- return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE;
-}
-
-export function getItemTitle( item: Post ) {
- if ( typeof item.title === 'string' ) {
- return decodeEntities( item.title );
- }
- if ( 'rendered' in item.title ) {
- return decodeEntities( item.title.rendered );
- }
- if ( 'raw' in item.title ) {
- return decodeEntities( item.title.raw );
- }
- return '';
-}
-
-/**
- * Check if a template is removable.
- *
- * @param template The template entity to check.
- * @return Whether the template is removable.
- */
-export function isTemplateRemovable( template: Template | TemplatePart ) {
- if ( ! template ) {
- return false;
- }
- // In patterns list page we map the templates parts to a different object
- // than the one returned from the endpoint. This is why we need to check for
- // two props whether is custom or has a theme file.
- return (
- [ template.source, template.source ].includes(
- TEMPLATE_ORIGINS.custom
- ) &&
- ! Boolean( template.type === 'wp_template' && template?.plugin ) &&
- ! template.has_theme_file
- );
-}
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index 9e8d184e34d3a..e61ade7e83036 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -8,9 +8,9 @@ import { doAction } from '@wordpress/hooks';
/**
* Internal dependencies
*/
-import type { PostType } from '../types';
import { store as editorStore } from '../../store';
import { unlock } from '../../lock-unlock';
+import type { PostType } from '@wordpress/fields';
import {
viewPost,
viewPostRevisions,
@@ -24,6 +24,7 @@ import {
renamePost,
resetPost,
deletePost,
+ duplicateTemplatePart,
featuredImageField,
dateField,
parentField,
@@ -34,7 +35,6 @@ import {
authorField,
titleField,
} from '@wordpress/fields';
-import duplicateTemplatePart from '../actions/duplicate-template-part';
export function registerEntityAction< Item >(
kind: string,
diff --git a/packages/editor/src/dataviews/types.ts b/packages/editor/src/dataviews/types.ts
deleted file mode 100644
index 9549e6c4aa374..0000000000000
--- a/packages/editor/src/dataviews/types.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-type PostStatus =
- | 'publish'
- | 'draft'
- | 'pending'
- | 'private'
- | 'future'
- | 'auto-draft'
- | 'trash';
-
-export interface CommonPost {
- status?: PostStatus;
- title: string | { rendered: string } | { raw: string };
- content: string | { raw: string; rendered: string };
- type: string;
- id: string | number;
- blocks?: Object[];
- _links?: Links;
-}
-
-interface Links {
- 'predecessor-version'?: { href: string; id: number }[];
- 'version-history'?: { href: string; count: number }[];
- [ key: string ]: { href: string }[] | undefined;
-}
-
-export interface BasePost extends CommonPost {
- comment_status?: 'open' | 'closed';
- excerpt?: string | { raw: string; rendered: string };
- meta?: Record< string, any >;
- parent?: number;
- password?: string;
- template?: string;
- format?: string;
- featured_media?: number;
- menu_order?: number;
- ping_status?: 'open' | 'closed';
- link?: string;
-}
-
-export interface Template extends CommonPost {
- type: 'wp_template';
- is_custom: boolean;
- source: string;
- origin: string;
- plugin?: string;
- has_theme_file: boolean;
- id: string;
-}
-
-export interface TemplatePart extends CommonPost {
- type: 'wp_template_part';
- source: string;
- origin: string;
- has_theme_file: boolean;
- id: string;
- area: string;
-}
-
-export interface Pattern extends CommonPost {
- slug: string;
- title: { raw: string };
- wp_pattern_sync_status: string;
-}
-
-export type Post = Template | TemplatePart | Pattern | BasePost;
-
-export type PostWithPermissions = Post & {
- permissions: {
- delete: boolean;
- update: boolean;
- };
-};
-
-export interface PostType {
- slug: string;
- viewable: boolean;
- supports?: {
- 'page-attributes'?: boolean;
- title?: boolean;
- revisions?: boolean;
- thumbnail?: boolean;
- comments?: boolean;
- author?: boolean;
- };
-}
-
-// Will be unnecessary after typescript 5.0 upgrade.
-export type CoreDataError = { message?: string; code?: string };
diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js
index 2699858b3164f..11083eb6ab8a4 100644
--- a/packages/editor/src/private-apis.js
+++ b/packages/editor/src/private-apis.js
@@ -10,7 +10,6 @@ import { lock } from './lock-unlock';
import { EntitiesSavedStatesExtensible } from './components/entities-saved-states';
import EditorContentSlotFill from './components/editor-interface/content-slot-fill';
import BackButton from './components/header/back-button';
-import CreateTemplatePartModal from './components/create-template-part-modal';
import Editor from './components/editor';
import PluginPostExcerpt from './components/post-excerpt/plugin';
import PostCardPanel from './components/post-card-panel';
@@ -24,6 +23,7 @@ import {
mergeBaseAndUserConfigs,
GlobalStylesProvider,
} from './components/global-styles-provider';
+import { CreateTemplatePartModal } from '@wordpress/fields';
import { registerCoreBlockBindingsSources } from './bindings/api';
import { getTemplateInfo } from './utils/get-template-info';
diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss
index 1504211a51e89..1a8103ae2b16c 100644
--- a/packages/editor/src/style.scss
+++ b/packages/editor/src/style.scss
@@ -3,7 +3,6 @@
@import "./components/autocompleters/style.scss";
@import "./components/collab-sidebar/style.scss";
@import "./components/collapsible-block-toolbar/style.scss";
-@import "./components/create-template-part-modal/style.scss";
@import "./components/block-settings-menu/style.scss";
@import "./components/blog-title/style.scss";
@import "./components/document-bar/style.scss";
diff --git a/packages/fields/README.md b/packages/fields/README.md
index e6cf6d3007ed9..6723611d2d968 100644
--- a/packages/fields/README.md
+++ b/packages/fields/README.md
@@ -18,10 +18,23 @@ npm install @wordpress/fields --save
Author field for BasePost.
+### BasePostWithEmbeddedAuthor
+
+Undocumented declaration.
+
### commentStatusField
Comment status field for BasePost.
+### CreateTemplatePartModal
+
+A React component that renders a modal for creating a template part. The modal displays a title and the contents for creating the template part. This component should not live in this package, it should be moved to a dedicated package responsible for managing template.
+
+_Parameters_
+
+- _props_ `Object`: The component props.
+- _props.modalTitle_ `{ modalTitle: string; } & CreateTemplatePartModalContentsProps[ 'modalTitle' ]`:
+
### dateField
Date field for BasePost.
@@ -42,6 +55,14 @@ Undocumented declaration.
Undocumented declaration.
+### duplicateTemplatePart
+
+This action is used to duplicate a template part.
+
+_Type_
+
+- `Action< TemplatePart >`
+
### exportPattern
Undocumented declaration.
@@ -70,6 +91,10 @@ This field is used to display the post password.
Undocumented declaration.
+### PostType
+
+Undocumented declaration.
+
### renamePost
Undocumented declaration.
diff --git a/packages/editor/src/dataviews/actions/duplicate-template-part.tsx b/packages/fields/src/actions/duplicate-template-part.tsx
similarity index 87%
rename from packages/editor/src/dataviews/actions/duplicate-template-part.tsx
rename to packages/fields/src/actions/duplicate-template-part.tsx
index 5f576ecdb5863..44fcbad21f073 100644
--- a/packages/editor/src/dataviews/actions/duplicate-template-part.tsx
+++ b/packages/fields/src/actions/duplicate-template-part.tsx
@@ -12,15 +12,17 @@ import type { Action } from '@wordpress/dataviews';
/**
* Internal dependencies
*/
-import { TEMPLATE_PART_POST_TYPE } from '../../store/constants';
-import { CreateTemplatePartModalContents } from '../../components/create-template-part-modal';
-import { getItemTitle } from './utils';
import type { Post, TemplatePart } from '../types';
+import { CreateTemplatePartModalContents } from '../components/create-template-part-modal';
+import { getItemTitle } from './utils';
+/**
+ * This action is used to duplicate a template part.
+ */
const duplicateTemplatePart: Action< TemplatePart > = {
id: 'duplicate-template-part',
label: _x( 'Duplicate', 'action label' ),
- isEligible: ( item ) => item.type === TEMPLATE_PART_POST_TYPE,
+ isEligible: ( item ) => item.type === 'wp_template_part',
modalHeader: _x( 'Duplicate template part', 'action label' ),
RenderModal: ( { items, closeModal } ) => {
const [ item ] = items;
@@ -61,7 +63,7 @@ const duplicateTemplatePart: Action< TemplatePart > = {
onCreate={ onTemplatePartSuccess }
onError={ closeModal }
confirmLabel={ _x( 'Duplicate', 'action label' ) }
- closeModal={ closeModal }
+ closeModal={ closeModal ?? ( () => {} ) }
/>
);
},
diff --git a/packages/fields/src/actions/index.ts b/packages/fields/src/actions/index.ts
index 08e22836e68fd..fc66220677531 100644
--- a/packages/fields/src/actions/index.ts
+++ b/packages/fields/src/actions/index.ts
@@ -13,3 +13,4 @@ export { default as permanentlyDeletePost } from './permanently-delete-post';
export { default as restorePost } from './restore-post';
export { default as trashPost } from './trash-post';
export { default as deletePost } from './delete-post';
+export { default as duplicateTemplatePart } from './duplicate-template-part';
diff --git a/packages/fields/src/actions/rename-post.tsx b/packages/fields/src/actions/rename-post.tsx
index da1fd46669f0d..5203328b46de5 100644
--- a/packages/fields/src/actions/rename-post.tsx
+++ b/packages/fields/src/actions/rename-post.tsx
@@ -26,9 +26,6 @@ import {
isTemplateRemovable,
isTemplate,
isTemplatePart,
- TEMPLATE_ORIGINS,
- TEMPLATE_PART_POST_TYPE,
- TEMPLATE_POST_TYPE,
} from './utils';
import type { CoreDataError, PostWithPermissions } from '../types';
@@ -45,8 +42,8 @@ const renamePost: Action< PostWithPermissions > = {
// Templates, template parts and patterns have special checks for renaming.
if (
! [
- TEMPLATE_POST_TYPE,
- TEMPLATE_PART_POST_TYPE,
+ 'wp_template',
+ 'wp_template_part',
...Object.values( PATTERN_TYPES ),
].includes( post.type )
) {
@@ -64,7 +61,7 @@ const renamePost: Action< PostWithPermissions > = {
if ( isTemplatePart( post ) ) {
return (
- post.source === TEMPLATE_ORIGINS.custom &&
+ post.source === 'custom' &&
! post?.has_theme_file &&
post.permissions?.update
);
diff --git a/packages/fields/src/actions/reset-post.tsx b/packages/fields/src/actions/reset-post.tsx
index 105d7b283b833..3e6b2e29b68b6 100644
--- a/packages/fields/src/actions/reset-post.tsx
+++ b/packages/fields/src/actions/reset-post.tsx
@@ -22,12 +22,7 @@ import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
-import {
- getItemTitle,
- isTemplateOrTemplatePart,
- TEMPLATE_ORIGINS,
- TEMPLATE_POST_TYPE,
-} from './utils';
+import { getItemTitle, isTemplateOrTemplatePart } from './utils';
import type { CoreDataError, Template, TemplatePart } from '../types';
const isTemplateRevertable = (
@@ -38,7 +33,7 @@ const isTemplateRevertable = (
}
return (
- templateOrTemplatePart.source === TEMPLATE_ORIGINS.custom &&
+ templateOrTemplatePart.source === 'custom' &&
( Boolean( templateOrTemplatePart?.plugin ) ||
templateOrTemplatePart?.has_theme_file )
);
@@ -186,7 +181,7 @@ const resetPostAction: Action< Template | TemplatePart > = {
isEligible: ( item ) => {
return (
isTemplateOrTemplatePart( item ) &&
- item?.source === TEMPLATE_ORIGINS.custom &&
+ item?.source === 'custom' &&
( Boolean( item.type === 'wp_template' && item?.plugin ) ||
item?.has_theme_file )
);
@@ -231,7 +226,7 @@ const resetPostAction: Action< Template | TemplatePart > = {
);
} catch ( error ) {
let fallbackErrorMessage;
- if ( items[ 0 ].type === TEMPLATE_POST_TYPE ) {
+ if ( items[ 0 ].type === 'wp_template' ) {
fallbackErrorMessage =
items.length === 1
? __(
diff --git a/packages/fields/src/actions/utils.ts b/packages/fields/src/actions/utils.ts
index 8f990fb1168fc..efd389405b5be 100644
--- a/packages/fields/src/actions/utils.ts
+++ b/packages/fields/src/actions/utils.ts
@@ -8,26 +8,18 @@ import { decodeEntities } from '@wordpress/html-entities';
*/
import type { Post, TemplatePart, Template } from '../types';
-export const TEMPLATE_POST_TYPE = 'wp_template';
-export const TEMPLATE_PART_POST_TYPE = 'wp_template_part';
-export const TEMPLATE_ORIGINS = {
- custom: 'custom',
- theme: 'theme',
- plugin: 'plugin',
-};
-
export function isTemplate( post: Post ): post is Template {
- return post.type === TEMPLATE_POST_TYPE;
+ return post.type === 'wp_template';
}
export function isTemplatePart( post: Post ): post is TemplatePart {
- return post.type === TEMPLATE_PART_POST_TYPE;
+ return post.type === 'wp_template_part';
}
export function isTemplateOrTemplatePart(
p: Post
): p is Template | TemplatePart {
- return p.type === TEMPLATE_POST_TYPE || p.type === TEMPLATE_PART_POST_TYPE;
+ return p.type === 'wp_template' || p.type === 'wp_template_part';
}
export function getItemTitle( item: Post ): string {
@@ -57,9 +49,7 @@ export function isTemplateRemovable( template: Template | TemplatePart ) {
// than the one returned from the endpoint. This is why we need to check for
// two props whether is custom or has a theme file.
return (
- [ template.source, template.source ].includes(
- TEMPLATE_ORIGINS.custom
- ) &&
+ [ template.source, template.source ].includes( 'custom' ) &&
! Boolean( template.type === 'wp_template' && template?.plugin ) &&
! template.has_theme_file
);
diff --git a/packages/editor/src/components/create-template-part-modal/index.js b/packages/fields/src/components/create-template-part-modal/index.tsx
similarity index 52%
rename from packages/editor/src/components/create-template-part-modal/index.js
rename to packages/fields/src/components/create-template-part-modal/index.tsx
index 660439ded2300..03b39a8fdcdf3 100644
--- a/packages/editor/src/components/create-template-part-modal/index.js
+++ b/packages/fields/src/components/create-template-part-modal/index.tsx
@@ -1,7 +1,6 @@
/**
* WordPress dependencies
*/
-import { useSelect, useDispatch } from '@wordpress/data';
import {
Icon,
BaseControl,
@@ -16,34 +15,57 @@ import {
__experimentalHStack as HStack,
__experimentalVStack as VStack,
} from '@wordpress/components';
-import { __ } from '@wordpress/i18n';
-import { useState } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
-import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore } from '@wordpress/core-data';
-import { check } from '@wordpress/icons';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { useState } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import {
+ check,
+ footer as footerIcon,
+ header as headerIcon,
+ sidebar as sidebarIcon,
+ symbolFilled as symbolFilledIcon,
+} from '@wordpress/icons';
+import { store as noticesStore } from '@wordpress/notices';
+// @ts-ignore
import { serialize } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import {
- TEMPLATE_PART_POST_TYPE,
- TEMPLATE_PART_AREA_DEFAULT_CATEGORY,
-} from '../../store/constants';
-import {
- useExistingTemplateParts,
- getUniqueTemplatePartTitle,
getCleanTemplatePartSlug,
+ getUniqueTemplatePartTitle,
+ useExistingTemplateParts,
} from './utils';
+type CreateTemplatePartModalContentsProps = {
+ defaultArea?: string;
+ blocks: any[];
+ confirmLabel?: string;
+ closeModal: () => void;
+ onCreate: ( templatePart: any ) => void;
+ onError?: () => void;
+ defaultTitle?: string;
+};
+
+/**
+ * A React component that renders a modal for creating a template part. The modal displays a title and the contents for creating the template part.
+ * This component should not live in this package, it should be moved to a dedicated package responsible for managing template.
+ * @param {Object} props The component props.
+ * @param props.modalTitle
+ */
export default function CreateTemplatePartModal( {
modalTitle,
...restProps
-} ) {
+}: {
+ modalTitle: string;
+} & CreateTemplatePartModalContentsProps ) {
const defaultModalTitle = useSelect(
( select ) =>
- select( coreStore ).getPostType( TEMPLATE_PART_POST_TYPE )?.labels
+ // @ts-ignore
+ select( coreStore ).getPostType( 'wp_template_part' )?.labels
?.add_new_item,
[]
);
@@ -51,24 +73,49 @@ export default function CreateTemplatePartModal( {
+ { /* @ts-ignore */ }
);
}
+const getTemplatePartIcon = ( iconName: string ) => {
+ if ( 'header' === iconName ) {
+ return headerIcon;
+ } else if ( 'footer' === iconName ) {
+ return footerIcon;
+ } else if ( 'sidebar' === iconName ) {
+ return sidebarIcon;
+ }
+ return symbolFilledIcon;
+};
+
+/**
+ * A React component that renders the content of a model for creating a template part.
+ * This component should not live in this package; it should be moved to a dedicated package responsible for managing template.
+ *
+ * @param {Object} props - The component props.
+ * @param {string} [props.defaultArea=uncategorized] - The default area for the template part.
+ * @param {Array} [props.blocks=[]] - The blocks to be included in the template part.
+ * @param {string} [props.confirmLabel='Add'] - The label for the confirm button.
+ * @param {Function} props.closeModal - Function to close the modal.
+ * @param {Function} props.onCreate - Function to call when the template part is successfully created.
+ * @param {Function} [props.onError] - Function to call when there is an error creating the template part.
+ * @param {string} [props.defaultTitle=''] - The default title for the template part.
+ */
export function CreateTemplatePartModalContents( {
- defaultArea = TEMPLATE_PART_AREA_DEFAULT_CATEGORY,
+ defaultArea = 'uncategorized',
blocks = [],
confirmLabel = __( 'Add' ),
closeModal,
onCreate,
onError,
defaultTitle = '',
-} ) {
+}: CreateTemplatePartModalContentsProps ) {
const { createErrorNotice } = useDispatch( noticesStore );
const { saveEntityRecord } = useDispatch( coreStore );
const existingTemplateParts = useExistingTemplateParts();
@@ -78,12 +125,22 @@ export function CreateTemplatePartModalContents( {
const [ isSubmitting, setIsSubmitting ] = useState( false );
const instanceId = useInstanceId( CreateTemplatePartModal );
- const templatePartAreas = useSelect(
- ( select ) =>
- select( coreStore ).getEntityRecord( 'root', '__unstableBase' )
- ?.default_template_part_areas || [],
- []
- );
+ const defaultTemplatePartAreas = useSelect( ( select ) => {
+ const areas =
+ // @ts-expect-error getEntityRecord is not typed with unstableBase as argument.
+ select( coreStore ).getEntityRecord< {
+ default_template_part_areas: Array< {
+ area: string;
+ label: string;
+ icon: string;
+ description: string;
+ } >;
+ } >( 'root', '__unstableBase' )?.default_template_part_areas || [];
+
+ return areas.map( ( item ) => {
+ return { ...item, icon: getTemplatePartIcon( item.icon ) };
+ } );
+ }, [] );
async function createTemplatePart() {
if ( ! title || isSubmitting ) {
@@ -100,7 +157,7 @@ export function CreateTemplatePartModalContents( {
const templatePart = await saveEntityRecord(
'postType',
- TEMPLATE_PART_POST_TYPE,
+ 'wp_template_part',
{
slug: cleanSlug,
title: uniqueTitle,
@@ -114,7 +171,10 @@ export function CreateTemplatePartModalContents( {
// TODO: Add a success notice?
} catch ( error ) {
const errorMessage =
- error.message && error.code !== 'unknown_error'
+ error instanceof Error &&
+ 'code' in error &&
+ error.message &&
+ error.code !== 'unknown_error'
? error.message
: __(
'An error occurred while creating the template part.'
@@ -146,34 +206,38 @@ export function CreateTemplatePartModalContents( {
+ value && typeof value === 'string'
+ ? setArea( value )
+ : () => void 0
+ }
checked={ area }
>
- { templatePartAreas.map(
+ { defaultTemplatePartAreas.map(
( { icon, label, area: value, description } ) => (
-
+
{ label }
{ description }
-
+
{ area === value && (
) }
diff --git a/packages/editor/src/components/create-template-part-modal/style.scss b/packages/fields/src/components/create-template-part-modal/style.scss
similarity index 69%
rename from packages/editor/src/components/create-template-part-modal/style.scss
rename to packages/fields/src/components/create-template-part-modal/style.scss
index be15e8d76d536..fedc0326648c2 100644
--- a/packages/editor/src/components/create-template-part-modal/style.scss
+++ b/packages/fields/src/components/create-template-part-modal/style.scss
@@ -1,13 +1,13 @@
-.editor-create-template-part-modal {
- z-index: z-index(".editor-create-template-part-modal");
+.fields-create-template-part-modal {
+ z-index: z-index(".fields-create-template-part-modal");
}
-.editor-create-template-part-modal__area-radio-group {
+.fields-create-template-part-modal__area-radio-group {
width: 100%;
border: $border-width solid $gray-700;
border-radius: $radius-small;
- .components-button.editor-create-template-part-modal__area-radio {
+ .components-button.fields-create-template-part-modal__area-radio {
display: block;
width: 100%;
height: 100%;
@@ -40,12 +40,12 @@
color: $gray-900;
cursor: auto;
- .editor-create-template-part-modal__option-label div {
+ .fields-create-template-part-modal__option-label div {
color: $gray-600;
}
}
- .editor-create-template-part-modal__option-label {
+ .fields-create-template-part-modal__option-label {
padding-top: $grid-unit-05;
white-space: normal;
@@ -55,7 +55,7 @@
}
}
- .editor-create-template-part-modal__checkbox {
+ .fields-create-template-part-modal__checkbox {
margin-left: auto;
min-width: $grid-unit-30;
}
diff --git a/packages/editor/src/components/create-template-part-modal/test/utils.js b/packages/fields/src/components/create-template-part-modal/test/utils.js
similarity index 100%
rename from packages/editor/src/components/create-template-part-modal/test/utils.js
rename to packages/fields/src/components/create-template-part-modal/test/utils.js
diff --git a/packages/editor/src/components/create-template-part-modal/utils.js b/packages/fields/src/components/create-template-part-modal/utils.js
similarity index 86%
rename from packages/editor/src/components/create-template-part-modal/utils.js
rename to packages/fields/src/components/create-template-part-modal/utils.js
index 02f24cf17d2be..9ecf3efd03f91 100644
--- a/packages/editor/src/components/create-template-part-modal/utils.js
+++ b/packages/fields/src/components/create-template-part-modal/utils.js
@@ -12,19 +12,20 @@ import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
-import { TEMPLATE_PART_POST_TYPE } from '../../store/constants';
export const useExistingTemplateParts = () => {
- return useSelect(
- ( select ) =>
- select( coreStore ).getEntityRecords(
- 'postType',
- TEMPLATE_PART_POST_TYPE,
- {
- per_page: -1,
- }
- ),
- []
+ return (
+ useSelect(
+ ( select ) =>
+ select( coreStore ).getEntityRecords(
+ 'postType',
+ 'wp_template_part',
+ {
+ per_page: -1,
+ }
+ ),
+ []
+ ) ?? []
);
};
diff --git a/packages/fields/src/index.ts b/packages/fields/src/index.ts
index 41879a86e76be..1658c9d8c51ee 100644
--- a/packages/fields/src/index.ts
+++ b/packages/fields/src/index.ts
@@ -1,3 +1,4 @@
export * from './fields';
export * from './actions';
-export type * from './types';
+export { default as CreateTemplatePartModal } from './components/create-template-part-modal';
+export type { BasePostWithEmbeddedAuthor, PostType } from './types';
diff --git a/packages/fields/src/style.scss b/packages/fields/src/style.scss
index 1639f455ba093..05cf565224877 100644
--- a/packages/fields/src/style.scss
+++ b/packages/fields/src/style.scss
@@ -1,2 +1,3 @@
+@import "./components/create-template-part-modal/style.scss";
@import "./fields/slug/style.scss";
@import "./fields/featured-image/style.scss";
diff --git a/packages/fields/src/types.ts b/packages/fields/src/types.ts
index e457ec699554c..1b251d125b1be 100644
--- a/packages/fields/src/types.ts
+++ b/packages/fields/src/types.ts
@@ -97,6 +97,9 @@ export interface PostType {
'page-attributes'?: boolean;
title?: boolean;
revisions?: boolean;
+ author?: string;
+ thumbnail?: string;
+ comments?: string;
};
}
diff --git a/packages/fields/tsconfig.json b/packages/fields/tsconfig.json
index 5c4b91e88f895..531afb5bb2d87 100644
--- a/packages/fields/tsconfig.json
+++ b/packages/fields/tsconfig.json
@@ -12,6 +12,7 @@
{ "path": "../components" },
{ "path": "../compose" },
{ "path": "../core-data" },
+ { "path": "../block-editor" },
{ "path": "../data" },
{ "path": "../dataviews" },
{ "path": "../date" },
From fa438ca92e3a2fe212b6f573cb41937f574199a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?=
Date: Tue, 3 Dec 2024 11:28:05 +0100
Subject: [PATCH 046/600] =?UTF-8?q?Revert=20"Extensibility:=20Make=20Block?=
=?UTF-8?q?=20Bindings=20work=20with=20`editor.BlockEdit`=20hook=20?=
=?UTF-8?q?=E2=80=A6"=20(#67516)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit c517e410017f4d65a7c6f03a31c5c2fa15cbbd65.
Co-authored-by: gziolo
Co-authored-by: SantosGuillamot
---
.../src/components/block-edit/edit.js | 12 +-
.../block-list/use-block-props/index.js | 2 +-
.../src/components/rich-text/index.js | 2 +-
.../block-editor/src/hooks/block-bindings.js | 10 +-
packages/block-editor/src/hooks/index.js | 1 +
.../use-bindings-attributes.js} | 103 ++++++++++++++----
.../block-editor/src/utils/block-bindings.js | 37 -------
packages/e2e-tests/plugins/block-bindings.php | 6 +-
.../e2e-tests/plugins/block-bindings/index.js | 45 --------
.../various/block-bindings/post-meta.spec.js | 41 -------
10 files changed, 94 insertions(+), 165 deletions(-)
rename packages/block-editor/src/{components/block-edit/with-block-bindings-support.js => hooks/use-bindings-attributes.js} (73%)
diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js
index 6b1ddd86f4c76..83d0e3f406f82 100644
--- a/packages/block-editor/src/components/block-edit/edit.js
+++ b/packages/block-editor/src/components/block-edit/edit.js
@@ -18,8 +18,6 @@ import { useContext, useMemo } from '@wordpress/element';
* Internal dependencies
*/
import BlockContext from '../block-context';
-import { withBlockBindingsSupport } from './with-block-bindings-support';
-import { canBindBlock } from '../../utils/block-bindings';
/**
* Default value used for blocks which do not define their own context needs,
@@ -49,8 +47,6 @@ const Edit = ( props ) => {
const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit );
-const EditWithFiltersAndBindings = withBlockBindingsSupport( EditWithFilters );
-
const EditWithGeneratedProps = ( props ) => {
const { attributes = {}, name } = props;
const blockType = getBlockType( name );
@@ -71,12 +67,8 @@ const EditWithGeneratedProps = ( props ) => {
return null;
}
- const EditComponent = canBindBlock( name )
- ? EditWithFiltersAndBindings
- : EditWithFilters;
-
if ( blockType.apiVersion > 1 ) {
- return ;
+ return ;
}
// Generate a class name for the block's editable form.
@@ -90,7 +82,7 @@ const EditWithGeneratedProps = ( props ) => {
);
return (
- ( props ) => {
const registry = useRegistry();
const blockContext = useContext( BlockContext );
@@ -72,9 +108,9 @@ export const withBlockBindingsSupport = createHigherOrderComponent(
() =>
replacePatternOverrideDefaultBindings(
name,
- props.attributes?.metadata?.bindings
+ props.attributes.metadata?.bindings
),
- [ props.attributes?.metadata?.bindings, name ]
+ [ props.attributes.metadata?.bindings, name ]
);
// While this hook doesn't directly call any selectors, `useSelect` is
@@ -160,7 +196,7 @@ export const withBlockBindingsSupport = createHigherOrderComponent(
const hasParentPattern = !! updatedContext[ 'pattern/overrides' ];
const hasPatternOverridesDefaultBinding =
- props.attributes?.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
+ props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ]
?.source === 'core/pattern-overrides';
const _setAttributes = useCallback(
@@ -247,13 +283,40 @@ export const withBlockBindingsSupport = createHigherOrderComponent(
);
return (
-
+ <>
+
+ >
);
},
'withBlockBindingSupport'
);
+
+/**
+ * Filters a registered block's settings to enhance a block's `edit` component
+ * to upgrade bound attributes.
+ *
+ * @param {WPBlockSettings} settings - Registered block settings.
+ * @param {string} name - Block name.
+ * @return {WPBlockSettings} Filtered block settings.
+ */
+function shimAttributeSource( settings, name ) {
+ if ( ! canBindBlock( name ) ) {
+ return settings;
+ }
+
+ return {
+ ...settings,
+ edit: withBlockBindingSupport( settings.edit ),
+ };
+}
+
+addFilter(
+ 'blocks.registerBlockType',
+ 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source',
+ shimAttributeSource
+);
diff --git a/packages/block-editor/src/utils/block-bindings.js b/packages/block-editor/src/utils/block-bindings.js
index 82f0dff53531a..dcf80d985473b 100644
--- a/packages/block-editor/src/utils/block-bindings.js
+++ b/packages/block-editor/src/utils/block-bindings.js
@@ -13,43 +13,6 @@ function isObjectEmpty( object ) {
return ! object || Object.keys( object ).length === 0;
}
-export const BLOCK_BINDINGS_ALLOWED_BLOCKS = {
- 'core/paragraph': [ 'content' ],
- 'core/heading': [ 'content' ],
- 'core/image': [ 'id', 'url', 'title', 'alt' ],
- 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ],
-};
-
-/**
- * Based on the given block name,
- * check if it is possible to bind the block.
- *
- * @param {string} blockName - The block name.
- * @return {boolean} Whether it is possible to bind the block to sources.
- */
-export function canBindBlock( blockName ) {
- return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS;
-}
-
-/**
- * Based on the given block name and attribute name,
- * check if it is possible to bind the block attribute.
- *
- * @param {string} blockName - The block name.
- * @param {string} attributeName - The attribute name.
- * @return {boolean} Whether it is possible to bind the block attribute.
- */
-export function canBindAttribute( blockName, attributeName ) {
- return (
- canBindBlock( blockName ) &&
- BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName )
- );
-}
-
-export function getBindableAttributes( blockName ) {
- return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ];
-}
-
/**
* Contains utils to update the block `bindings` metadata.
*
diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php
index 1fd6d8468c77d..b86673c2c523d 100644
--- a/packages/e2e-tests/plugins/block-bindings.php
+++ b/packages/e2e-tests/plugins/block-bindings.php
@@ -41,11 +41,7 @@ function gutenberg_test_block_bindings_registration() {
plugins_url( 'block-bindings/index.js', __FILE__ ),
array(
'wp-blocks',
- 'wp-block-editor',
- 'wp-components',
- 'wp-compose',
- 'wp-element',
- 'wp-hooks',
+ 'wp-private-apis',
),
filemtime( plugin_dir_path( __FILE__ ) . 'block-bindings/index.js' ),
true
diff --git a/packages/e2e-tests/plugins/block-bindings/index.js b/packages/e2e-tests/plugins/block-bindings/index.js
index 63c463e197fa8..5c364257caed1 100644
--- a/packages/e2e-tests/plugins/block-bindings/index.js
+++ b/packages/e2e-tests/plugins/block-bindings/index.js
@@ -1,9 +1,4 @@
const { registerBlockBindingsSource } = wp.blocks;
-const { InspectorControls } = wp.blockEditor;
-const { PanelBody, TextControl } = wp.components;
-const { createHigherOrderComponent } = wp.compose;
-const { createElement: el, Fragment } = wp.element;
-const { addFilter } = wp.hooks;
const { fieldsList } = window.testingBindings || {};
const getValues = ( { bindings } ) => {
@@ -51,43 +46,3 @@ registerBlockBindingsSource( {
getValues,
canUserEditValue: () => true,
} );
-
-const withBlockBindingsInspectorControl = createHigherOrderComponent(
- ( BlockEdit ) => {
- return ( props ) => {
- if ( ! props.attributes?.metadata?.bindings?.content ) {
- return el( BlockEdit, props );
- }
-
- return el(
- Fragment,
- {},
- el( BlockEdit, props ),
- el(
- InspectorControls,
- {},
- el(
- PanelBody,
- { title: 'Bindings' },
- el( TextControl, {
- __next40pxDefaultSize: true,
- __nextHasNoMarginBottom: true,
- label: 'Content',
- value: props.attributes.content,
- onChange: ( content ) =>
- props.setAttributes( {
- content,
- } ),
- } )
- )
- )
- );
- };
- }
-);
-
-addFilter(
- 'editor.BlockEdit',
- 'testing/bindings-inspector-control',
- withBlockBindingsInspectorControl
-);
diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
index 318707e22f098..32334bfc777f2 100644
--- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
+++ b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js
@@ -524,47 +524,6 @@ test.describe( 'Post Meta source', () => {
previewPage.locator( '#connected-paragraph' )
).toHaveText( 'new value' );
} );
-
- test( 'should be possible to edit the value of the connected custom fields in the inspector control registered by the plugin', async ( {
- editor,
- page,
- } ) => {
- await editor.insertBlock( {
- name: 'core/paragraph',
- attributes: {
- anchor: 'connected-paragraph',
- content: 'fallback content',
- metadata: {
- bindings: {
- content: {
- source: 'core/post-meta',
- args: {
- key: 'movie_field',
- },
- },
- },
- },
- },
- } );
- const contentInput = page.getByRole( 'textbox', {
- name: 'Content',
- } );
- await expect( contentInput ).toHaveValue(
- 'Movie field default value'
- );
- await contentInput.fill( 'new value' );
- // Check that the paragraph content attribute didn't change.
- const [ paragraphBlockObject ] = await editor.getBlocks();
- expect( paragraphBlockObject.attributes.content ).toBe(
- 'fallback content'
- );
- // Check the value of the custom field is being updated by visiting the frontend.
- const previewPage = await editor.openPreviewPage();
- await expect(
- previewPage.locator( '#connected-paragraph' )
- ).toHaveText( 'new value' );
- } );
-
test( 'should be possible to connect movie fields through the attributes panel', async ( {
editor,
page,
From fcee058e3bb787ee041c170958e15e66a6302352 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?=
Date: Tue, 3 Dec 2024 11:44:23 +0100
Subject: [PATCH 047/600] Scripts: Make React Fast Refresh work with multiple
blocks (#64924)
* Scripts: Make React Fast Refresh Work with multiple blocks
* Properly pass the unique name for the build and updates test snapshots
* Apply suggestions from code review
* Add changelog entry
Co-authored-by: gziolo
Co-authored-by: sirreal
---
.../lib/index.js | 10 +++++++
.../test/__snapshots__/build.js.snap | 12 ++++----
packages/scripts/CHANGELOG.md | 4 +++
packages/scripts/config/webpack.config.js | 29 ++++++++++++++++++-
4 files changed, 48 insertions(+), 7 deletions(-)
diff --git a/packages/dependency-extraction-webpack-plugin/lib/index.js b/packages/dependency-extraction-webpack-plugin/lib/index.js
index cf780d7370dcf..8bc7cb2931216 100644
--- a/packages/dependency-extraction-webpack-plugin/lib/index.js
+++ b/packages/dependency-extraction-webpack-plugin/lib/index.js
@@ -387,6 +387,16 @@ class DependencyExtractionWebpackPlugin {
assetData.type = 'module';
}
+ if ( compilation.options?.optimization?.runtimeChunk !== false ) {
+ // Sets the script handle for the shared runtime file so WordPress registers it only once when using the asset file.
+ assetData.handle =
+ compilation.name +
+ '-' +
+ chunkJSFile
+ .replace( /\\/g, '/' )
+ .replace( jsExtensionRegExp, '' );
+ }
+
if ( combineAssets ) {
combinedAssetsData[ chunkJSFile ] = assetData;
continue;
diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap
index 8ea40b00d7c2d..bafae8eb91486 100644
--- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap
+++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap
@@ -265,17 +265,17 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`polyfill-magic-comm
exports[`DependencyExtractionWebpackPlugin modules Webpack \`polyfill-magic-comment-minified\` should produce expected output: External modules should match snapshot 1`] = `[]`;
exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = `
-" array('@wordpress/blob'), 'version' => 'ee5ac21a1f0003d732e6', 'type' => 'module');
+" array('@wordpress/blob'), 'version' => 'ee5ac21a1f0003d732e6', 'type' => 'module', 'handle' => 'runtime-chunk-single-modules-a');
"
`;
exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = `
-" array('@wordpress/blob', 'lodash'), 'version' => '5b112b32c6db548c2997', 'type' => 'module');
+" array('@wordpress/blob', 'lodash'), 'version' => '5b112b32c6db548c2997', 'type' => 'module', 'handle' => 'runtime-chunk-single-modules-b');
"
`;
exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = `
-" array(), 'version' => 'b1ca4106075e0bd94f9c', 'type' => 'module');
+" array(), 'version' => 'b1ca4106075e0bd94f9c', 'type' => 'module', 'handle' => 'runtime-chunk-single-modules-runtime');
"
`;
@@ -681,17 +681,17 @@ exports[`DependencyExtractionWebpackPlugin scripts Webpack \`polyfill-magic-comm
exports[`DependencyExtractionWebpackPlugin scripts Webpack \`polyfill-magic-comment-minified\` should produce expected output: External modules should match snapshot 1`] = `[]`;
exports[`DependencyExtractionWebpackPlugin scripts Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = `
-" array('wp-blob'), 'version' => 'd091f1cbbf7603d6e12c');
+" array('wp-blob'), 'version' => 'd091f1cbbf7603d6e12c', 'handle' => 'runtime-chunk-single-scripts-a');
"
`;
exports[`DependencyExtractionWebpackPlugin scripts Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = `
-" array('lodash', 'wp-blob'), 'version' => '845bc6d4ffbdb9419ebd');
+" array('lodash', 'wp-blob'), 'version' => '845bc6d4ffbdb9419ebd', 'handle' => 'runtime-chunk-single-scripts-b');
"
`;
exports[`DependencyExtractionWebpackPlugin scripts Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = `
-" array(), 'version' => '717eb779e609d175a7dd');
+" array(), 'version' => '717eb779e609d175a7dd', 'handle' => 'runtime-chunk-single-scripts-runtime');
"
`;
diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md
index 55143ae792ae5..ceaa25c4ff9a0 100644
--- a/packages/scripts/CHANGELOG.md
+++ b/packages/scripts/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Bug Fix
+
+- Make React Fast Refresh in the `start` command work with multiple blocks ([64924](https://github.com/WordPress/gutenberg/pull/64924)).
+
## 30.6.0 (2024-11-27)
## 30.5.1 (2024-11-18)
diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js
index f9ef7dc5b7acc..2c3d423fcbd1e 100644
--- a/packages/scripts/config/webpack.config.js
+++ b/packages/scripts/config/webpack.config.js
@@ -7,7 +7,7 @@ const CopyWebpackPlugin = require( 'copy-webpack-plugin' );
const webpack = require( 'webpack' );
const browserslist = require( 'browserslist' );
const MiniCSSExtractPlugin = require( 'mini-css-extract-plugin' );
-const { basename, dirname, resolve } = require( 'path' );
+const { basename, dirname, relative, resolve, sep } = require( 'path' );
const ReactRefreshWebpackPlugin = require( '@pmmmwh/react-refresh-webpack-plugin' );
const RtlCssPlugin = require( 'rtlcss-webpack-plugin' );
const TerserPlugin = require( 'terser-webpack-plugin' );
@@ -115,6 +115,7 @@ const baseConfig = {
optimization: {
// Only concatenate modules in production, when not analyzing bundles.
concatenateModules: isProduction && ! process.env.WP_BUNDLE_ANALYZER,
+ runtimeChunk: hasReactFastRefresh && 'single',
splitChunks: {
cacheGroups: {
style: {
@@ -340,6 +341,32 @@ const scriptConfig = {
}
} );
+ if ( hasReactFastRefresh ) {
+ // Prepends the file reference to the shared runtime chunk to every script type defined for the block.
+ const runtimePath = relative(
+ dirname( absoluteFrom ),
+ fromProjectRoot(
+ getWordPressSrcDirectory() +
+ sep +
+ 'runtime.js'
+ )
+ );
+ const fields =
+ getBlockJsonScriptFields( blockJson );
+ for ( const [ fieldName ] of Object.entries(
+ fields
+ ) ) {
+ blockJson[ fieldName ] = [
+ `file:${ runtimePath }`,
+ ...( Array.isArray(
+ blockJson[ fieldName ]
+ )
+ ? blockJson[ fieldName ]
+ : [ blockJson[ fieldName ] ] ),
+ ];
+ }
+ }
+
return JSON.stringify( blockJson, null, 2 );
}
From 5c76815eb6e47f8cdd8f32c919f431467b078366 Mon Sep 17 00:00:00 2001
From: Luigi Teschio
Date: Tue, 3 Dec 2024 12:14:33 +0100
Subject: [PATCH 048/600] Quick Edit: add Template field (#66591)
Co-authored-by: louwie17
Co-authored-by: gigitux
Co-authored-by: youknowriad
---
package-lock.json | 1 +
packages/base-styles/_z-index.scss | 1 +
.../src/components/post-edit/index.js | 36 ++-
.../src/dataviews/store/private-actions.ts | 2 +
packages/fields/README.md | 4 +
packages/fields/package.json | 1 +
packages/fields/src/actions/utils.ts | 4 +-
packages/fields/src/fields/index.ts | 1 +
packages/fields/src/fields/template/index.ts | 22 ++
.../fields/src/fields/template/style.scss | 23 ++
.../src/fields/template/template-edit.tsx | 210 ++++++++++++++++++
packages/fields/src/style.scss | 1 +
packages/fields/tsconfig.json | 1 +
13 files changed, 305 insertions(+), 2 deletions(-)
create mode 100644 packages/fields/src/fields/template/index.ts
create mode 100644 packages/fields/src/fields/template/style.scss
create mode 100644 packages/fields/src/fields/template/template-edit.tsx
diff --git a/package-lock.json b/package-lock.json
index 98865c9d041a5..834bb344016d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54694,6 +54694,7 @@
"@babel/runtime": "7.25.7",
"@wordpress/api-fetch": "*",
"@wordpress/blob": "*",
+ "@wordpress/block-editor": "*",
"@wordpress/blocks": "*",
"@wordpress/components": "*",
"@wordpress/compose": "*",
diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss
index c2ee8f698c2c8..af679edb91064 100644
--- a/packages/base-styles/_z-index.scss
+++ b/packages/base-styles/_z-index.scss
@@ -132,6 +132,7 @@ $z-layers: (
".editor-action-modal": 1000001,
".editor-post-template__swap-template-modal": 1000001,
".edit-site-template-panel__replace-template-modal": 1000001,
+ ".fields-controls__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/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js
index 3e75ef71d1ac9..9a99a987089c1 100644
--- a/packages/edit-site/src/components/post-edit/index.js
+++ b/packages/edit-site/src/components/post-edit/index.js
@@ -19,6 +19,8 @@ import { privateApis as editorPrivateApis } from '@wordpress/editor';
*/
import Page from '../page';
import { unlock } from '../../lock-unlock';
+import usePatternSettings from '../page-patterns/use-pattern-settings';
+import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
const { PostCardPanel, usePostFields } = unlock( editorPrivateApis );
@@ -85,6 +87,12 @@ function PostEditForm( { postType, postId } ) {
'slug',
'parent',
'comment_status',
+ {
+ label: __( 'Template' ),
+ labelPosition: 'side',
+ id: 'template',
+ layout: 'regular',
+ },
].filter(
( field ) =>
ids.length === 1 ||
@@ -123,6 +131,32 @@ function PostEditForm( { postType, postId } ) {
setMultiEdits( {} );
}, [ ids ] );
+ const { ExperimentalBlockEditorProvider } = unlock(
+ blockEditorPrivateApis
+ );
+ const settings = usePatternSettings();
+
+ /**
+ * The template field depends on the block editor settings.
+ * This is a workaround to ensure that the block editor settings are available.
+ * For more information, see: https://github.com/WordPress/gutenberg/issues/67521
+ */
+ const fieldsWithDependency = useMemo( () => {
+ return fields.map( ( field ) => {
+ if ( field.id === 'template' ) {
+ return {
+ ...field,
+ Edit: ( data ) => (
+
+
+
+ ),
+ };
+ }
+ return field;
+ } );
+ }, [ fields, settings ] );
+
return (
{ ids.length === 1 && (
@@ -130,7 +164,7 @@ function PostEditForm( { postType, postId } ) {
) }
diff --git a/packages/editor/src/dataviews/store/private-actions.ts b/packages/editor/src/dataviews/store/private-actions.ts
index e61ade7e83036..6906629fc8002 100644
--- a/packages/editor/src/dataviews/store/private-actions.ts
+++ b/packages/editor/src/dataviews/store/private-actions.ts
@@ -34,6 +34,7 @@ import {
statusField,
authorField,
titleField,
+ templateField,
} from '@wordpress/fields';
export function registerEntityAction< Item >(
@@ -171,6 +172,7 @@ export const registerPostTypeSchema =
postTypeConfig.supports?.[ 'page-attributes' ] && parentField,
postTypeConfig.supports?.comments && commentStatusField,
passwordField,
+ templateField,
].filter( Boolean );
registry.batch( () => {
diff --git a/packages/fields/README.md b/packages/fields/README.md
index 6723611d2d968..2fc512b943264 100644
--- a/packages/fields/README.md
+++ b/packages/fields/README.md
@@ -123,6 +123,10 @@ Undocumented declaration.
Status field for BasePost.
+### templateField
+
+Undocumented declaration.
+
### titleField
Undocumented declaration.
diff --git a/packages/fields/package.json b/packages/fields/package.json
index eb60f448fc13e..beaaf4f981301 100644
--- a/packages/fields/package.json
+++ b/packages/fields/package.json
@@ -35,6 +35,7 @@
"@babel/runtime": "7.25.7",
"@wordpress/api-fetch": "*",
"@wordpress/blob": "*",
+ "@wordpress/block-editor": "*",
"@wordpress/blocks": "*",
"@wordpress/components": "*",
"@wordpress/compose": "*",
diff --git a/packages/fields/src/actions/utils.ts b/packages/fields/src/actions/utils.ts
index efd389405b5be..7bc08573f0b9f 100644
--- a/packages/fields/src/actions/utils.ts
+++ b/packages/fields/src/actions/utils.ts
@@ -22,7 +22,9 @@ export function isTemplateOrTemplatePart(
return p.type === 'wp_template' || p.type === 'wp_template_part';
}
-export function getItemTitle( item: Post ): string {
+export function getItemTitle( item: {
+ title: string | { rendered: string } | { raw: string };
+} ) {
if ( typeof item.title === 'string' ) {
return decodeEntities( item.title );
}
diff --git a/packages/fields/src/fields/index.ts b/packages/fields/src/fields/index.ts
index 5ea4235af1d96..2cdf89ee13fb0 100644
--- a/packages/fields/src/fields/index.ts
+++ b/packages/fields/src/fields/index.ts
@@ -2,6 +2,7 @@ export { default as slugField } from './slug';
export { default as titleField } from './title';
export { default as orderField } from './order';
export { default as featuredImageField } from './featured-image';
+export { default as templateField } from './template';
export { default as parentField } from './parent';
export { default as passwordField } from './password';
export { default as statusField } from './status';
diff --git a/packages/fields/src/fields/template/index.ts b/packages/fields/src/fields/template/index.ts
new file mode 100644
index 0000000000000..7315b4ba349b1
--- /dev/null
+++ b/packages/fields/src/fields/template/index.ts
@@ -0,0 +1,22 @@
+/**
+ * WordPress dependencies
+ */
+import type { Field } from '@wordpress/dataviews';
+
+/**
+ * Internal dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import type { BasePost } from '../../types';
+import { TemplateEdit } from './template-edit';
+
+const templateField: Field< BasePost > = {
+ id: 'template',
+ type: 'text',
+ label: __( 'Template' ),
+ getValue: ( { item } ) => item.template,
+ Edit: TemplateEdit,
+ enableSorting: false,
+};
+
+export default templateField;
diff --git a/packages/fields/src/fields/template/style.scss b/packages/fields/src/fields/template/style.scss
new file mode 100644
index 0000000000000..a0c2fafec7389
--- /dev/null
+++ b/packages/fields/src/fields/template/style.scss
@@ -0,0 +1,23 @@
+.fields-controls__template-modal {
+ z-index: z-index(".fields-controls__template-modal");
+}
+
+.fields-controls__template-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;
+ }
+}
diff --git a/packages/fields/src/fields/template/template-edit.tsx b/packages/fields/src/fields/template/template-edit.tsx
new file mode 100644
index 0000000000000..c17364568a457
--- /dev/null
+++ b/packages/fields/src/fields/template/template-edit.tsx
@@ -0,0 +1,210 @@
+/**
+ * WordPress dependencies
+ */
+import { useCallback, useMemo, useState } from '@wordpress/element';
+// @ts-ignore
+import { parse } from '@wordpress/blocks';
+import type { WpTemplate } from '@wordpress/core-data';
+import { store as coreStore } from '@wordpress/core-data';
+import type { DataFormControlProps } from '@wordpress/dataviews';
+
+/**
+ * Internal dependencies
+ */
+// @ts-expect-error block-editor is not typed correctly.
+import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor';
+import {
+ Button,
+ Dropdown,
+ MenuGroup,
+ MenuItem,
+ Modal,
+} from '@wordpress/components';
+import { useAsyncList } from '@wordpress/compose';
+import { useSelect } from '@wordpress/data';
+import { decodeEntities } from '@wordpress/html-entities';
+import { __ } from '@wordpress/i18n';
+import { getItemTitle } from '../../actions/utils';
+import type { BasePost } from '../../types';
+import { unlock } from '../../lock-unlock';
+
+export const TemplateEdit = ( {
+ data,
+ field,
+ onChange,
+}: DataFormControlProps< BasePost > ) => {
+ const { id } = field;
+ const postType = data.type;
+ const postId =
+ typeof data.id === 'number' ? data.id : parseInt( data.id, 10 );
+ const slug = data.slug;
+
+ const { availableTemplates, templates } = useSelect(
+ ( select ) => {
+ const allTemplates =
+ select( coreStore ).getEntityRecords< WpTemplate >(
+ 'postType',
+ 'wp_template',
+ {
+ per_page: -1,
+ post_type: postType,
+ }
+ ) ?? [];
+
+ const { getHomePage, getPostsPageId } = unlock(
+ select( coreStore )
+ );
+
+ const isPostsPage = getPostsPageId() === +postId;
+ const isFrontPage =
+ postType === 'page' && getHomePage()?.postId === +postId;
+
+ const allowSwitchingTemplate = ! isPostsPage && ! isFrontPage;
+
+ return {
+ templates: allTemplates,
+ availableTemplates: allowSwitchingTemplate
+ ? allTemplates.filter(
+ ( template ) =>
+ template.is_custom &&
+ template.slug !== data.template &&
+ !! template.content.raw // Skip empty templates.
+ )
+ : [],
+ };
+ },
+ [ data.template, postId, postType ]
+ );
+
+ 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 );
+
+ const value = field.getValue( { item: data } );
+
+ const currentTemplate = useSelect(
+ ( select ) => {
+ const foundTemplate = templates?.find(
+ ( template ) => template.slug === value
+ );
+
+ if ( foundTemplate ) {
+ return foundTemplate;
+ }
+
+ let slugToCheck;
+ // In `draft` status we might not have a slug available, so we use the `single`
+ // post type templates slug(ex page, single-post, single-product etc..).
+ // Pages do not need the `single` prefix in the slug to be prioritized
+ // through template hierarchy.
+ if ( slug ) {
+ slugToCheck =
+ postType === 'page'
+ ? `${ postType }-${ slug }`
+ : `single-${ postType }-${ slug }`;
+ } else {
+ slugToCheck =
+ postType === 'page' ? 'page' : `single-${ postType }`;
+ }
+
+ if ( postType ) {
+ const templateId = select( coreStore ).getDefaultTemplateId( {
+ slug: slugToCheck,
+ } );
+
+ return select( coreStore ).getEntityRecord(
+ 'postType',
+ 'wp_template',
+ templateId
+ );
+ }
+ },
+ [ postType, slug, templates, value ]
+ );
+
+ const [ showModal, setShowModal ] = useState( false );
+
+ const onChangeControl = useCallback(
+ ( newValue: string ) =>
+ onChange( {
+ [ id ]: newValue,
+ } ),
+ [ id, onChange ]
+ );
+
+ return (
+
+ (
+
+ { currentTemplate
+ ? getItemTitle( currentTemplate )
+ : '' }
+
+ ) }
+ renderContent={ ( { onToggle } ) => (
+
+ {
+ setShowModal( true );
+ onToggle();
+ } }
+ >
+ { __( 'Swap template' ) }
+
+ {
+ // The default template in a post is indicated by an empty string
+ value !== '' && (
+ {
+ onChangeControl( '' );
+ onToggle();
+ } }
+ >
+ { __( 'Use default template' ) }
+
+ )
+ }
+
+ ) }
+ />
+ { showModal && (
+ setShowModal( false ) }
+ overlayClassName="fields-controls__template-modal"
+ isFullScreen
+ >
+
+ {
+ onChangeControl( template.name );
+ setShowModal( false );
+ } }
+ />
+
+
+ ) }
+
+ );
+};
diff --git a/packages/fields/src/style.scss b/packages/fields/src/style.scss
index 05cf565224877..582044235aef1 100644
--- a/packages/fields/src/style.scss
+++ b/packages/fields/src/style.scss
@@ -1,3 +1,4 @@
@import "./components/create-template-part-modal/style.scss";
@import "./fields/slug/style.scss";
@import "./fields/featured-image/style.scss";
+@import "./fields/template/style.scss";
diff --git a/packages/fields/tsconfig.json b/packages/fields/tsconfig.json
index 531afb5bb2d87..46ac86d48e11e 100644
--- a/packages/fields/tsconfig.json
+++ b/packages/fields/tsconfig.json
@@ -27,6 +27,7 @@
{ "path": "../private-apis" },
{ "path": "../router" },
{ "path": "../url" },
+ { "path": "../block-editor" },
{ "path": "../warning" }
],
"include": [ "src" ]
From 4d225cc2ba6f09822227e7a820b8a555be7c4d48 Mon Sep 17 00:00:00 2001
From: Ella <4710635+ellatrix@users.noreply.github.com>
Date: Tue, 3 Dec 2024 13:22:28 +0100
Subject: [PATCH 049/600] [mini] Preload: add post type (#67518)
Co-authored-by: ellatrix
Co-authored-by: Mamaduka
---
backport-changelog/6.8/7695.md | 1 +
lib/compat/wordpress-6.8/preload.php | 1 +
2 files changed, 2 insertions(+)
diff --git a/backport-changelog/6.8/7695.md b/backport-changelog/6.8/7695.md
index d69a59f2d67d1..08b780e2afb0d 100644
--- a/backport-changelog/6.8/7695.md
+++ b/backport-changelog/6.8/7695.md
@@ -4,3 +4,4 @@ https://github.com/WordPress/wordpress-develop/pull/7695
* https://github.com/WordPress/gutenberg/pull/67465
* https://github.com/WordPress/gutenberg/pull/66579
* https://github.com/WordPress/gutenberg/pull/66654
+* https://github.com/WordPress/gutenberg/pull/67518
diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php
index 0a36ea7f7227d..0e887fc081bcb 100644
--- a/lib/compat/wordpress-6.8/preload.php
+++ b/lib/compat/wordpress-6.8/preload.php
@@ -22,6 +22,7 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) {
$route_for_post = rest_get_route_for_post( $post );
if ( $route_for_post ) {
$paths[] = add_query_arg( 'context', 'edit', $route_for_post );
+ $paths[] = add_query_arg( 'context', 'edit', '/wp/v2/types/' . $post->post_type );
if ( 'page' === $post->post_type ) {
$paths[] = add_query_arg(
'slug',
From 0ff919f2c6a94e657987a8440c75b5e4f2db0b13 Mon Sep 17 00:00:00 2001
From: Jorge Costa
Date: Tue, 3 Dec 2024 12:49:06 +0000
Subject: [PATCH 050/600] Docs: Remove invalid key projects links on the
documentation. (#67491)
Co-authored-by: jorgefilipecosta
---
docs/contributors/repository-management.md | 6 ------
1 file changed, 6 deletions(-)
diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md
index e57f762a60539..5bb971bfaf2ef 100644
--- a/docs/contributors/repository-management.md
+++ b/docs/contributors/repository-management.md
@@ -165,9 +165,3 @@ If you meet this criterion of several meaningful contributions having been accep
## Projects
We use [GitHub projects](https://github.com/WordPress/gutenberg/projects) to keep track of details that aren't immediately actionable, but that we want to keep around for future reference.
-
-Some key projects include:
-
-- [Phase 2](https://github.com/WordPress/gutenberg/projects/13) - Development tasks needed for Phase 2 of Gutenberg.
-- [Phase 2 design](https://github.com/WordPress/gutenberg/projects/21) - Tasks for design in Phase 2. Note: specific projects may have their own boards.
-- [Ideas](https://github.com/WordPress/gutenberg/projects/8) - Project containing tickets that, while closed for the time being, can be revisited in the future.
From 1c3cea43b0fca853f351e4bc08ba840df7de2469 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Gomes?=
Date: Tue, 3 Dec 2024 14:24:57 +0000
Subject: [PATCH 051/600] Exclude Set instance methods from polyfills (#67230)
* Exclude Set instance methods from polyfills
* Switch to regexp exclusions
---
.../polyfill-exclusions.js | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/packages/babel-preset-default/polyfill-exclusions.js b/packages/babel-preset-default/polyfill-exclusions.js
index 507396c930b99..ca8c045d12414 100644
--- a/packages/babel-preset-default/polyfill-exclusions.js
+++ b/packages/babel-preset-default/polyfill-exclusions.js
@@ -7,4 +7,25 @@ module.exports = [
// This is an IE-only feature which we don't use, and don't want to polyfill.
// @see https://github.com/WordPress/gutenberg/pull/49234
'web.immediate',
+ // Remove Set feature polyfills.
+ //
+ // The Babel/core-js integration has a severe limitation, in that any Set
+ // objects (e.g. `new Set()`) are assumed to need all instance methods, and
+ // get them all polyfilled. There is no validation as to whether those
+ // methods are actually in use.
+ //
+ // This limitation causes a number of packages to unnecessarily get a
+ // dependency on `wp-polyfill`, which in most cases gets loaded as part of
+ // the critical path and can thus have an impact on performance.
+ //
+ // There is no good solution to this, and the one we've opted for here is
+ // to disable polyfilling these features entirely. Developers will need to
+ // take care not to use them in scenarios where the code may be running in
+ // older browsers without native support for them.
+ //
+ // These need to be specified as both `es.` and `esnext.` due to the way
+ // internal dependencies are set up in Babel / core-js.
+ //
+ // @see https://github.com/WordPress/gutenberg/pull/67230
+ /^es(next)?\.set\./,
];
From 7631986644c82b2c8ff7481e95a64124644f7c1d Mon Sep 17 00:00:00 2001
From: Mitchell Austin
Date: Tue, 3 Dec 2024 08:24:33 -0800
Subject: [PATCH 052/600] Split view with meta boxes even with legacy canvas
(#66706)
* Split view with meta boxes with non-iframed canvas
* Fix scrolling of device previews
* Consolidate styles and add comments
* Do the same thing without adding a prop to BlockCanvas
* Fix horizontal overflow of device previews
Co-authored-by: stokesman
Co-authored-by: t-hamano
Co-authored-by: cbravobernal
Co-authored-by: jartes
Co-authored-by: bph
Co-authored-by: ndiego
Co-authored-by: MadtownLems
---
.../src/components/block-canvas/index.js | 8 ++---
.../src/components/use-resize-canvas/index.js | 2 +-
.../edit-post/src/components/layout/index.js | 23 +++---------
.../src/components/layout/style.scss | 8 ++---
.../components/editor-interface/style.scss | 3 +-
.../src/components/visual-editor/index.js | 1 +
.../src/components/visual-editor/style.scss | 35 +++++++++++++++----
7 files changed, 44 insertions(+), 36 deletions(-)
diff --git a/packages/block-editor/src/components/block-canvas/index.js b/packages/block-editor/src/components/block-canvas/index.js
index c399f38054ed4..36aca7fa1c722 100644
--- a/packages/block-editor/src/components/block-canvas/index.js
+++ b/packages/block-editor/src/components/block-canvas/index.js
@@ -56,7 +56,8 @@ export function ExperimentalBlockCanvas( {
return (
{ children }
@@ -81,6 +78,7 @@ export function ExperimentalBlockCanvas( {
return (