From 6df457c0e47036c3f015970f82661c30bf2803ec Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 5 Nov 2024 08:43:44 +1100 Subject: [PATCH 001/605] Block toolbar: restrict visible child calculation to known blocks (#66702) * Refactor getVisibleElementBounds to only check visible children for specific blocks that we know overflow. Rename function and update comments. Introduce rudimentary unit tests. * Apply suggestions to code comments from code review Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> * Add extra tests * Rebase * Added a test for viewport clipping and adjusted test descriptions --------- Co-authored-by: ramonjd Co-authored-by: aaronrobertshaw Co-authored-by: t-hamano Co-authored-by: ndiego Co-authored-by: getdave Co-authored-by: stokesman Co-authored-by: andrewserong Co-authored-by: simom --- .../src/components/block-popover/index.js | 8 +- .../use-block-toolbar-popover-props.js | 4 +- packages/block-editor/src/utils/dom.js | 46 ++-- packages/block-editor/src/utils/test/dom.js | 224 ++++++++++++++++++ 4 files changed, 256 insertions(+), 26 deletions(-) create mode 100644 packages/block-editor/src/utils/test/dom.js diff --git a/packages/block-editor/src/components/block-popover/index.js b/packages/block-editor/src/components/block-popover/index.js index f01c43ef26a71d..06409bc6e65f42 100644 --- a/packages/block-editor/src/components/block-popover/index.js +++ b/packages/block-editor/src/components/block-popover/index.js @@ -20,7 +20,7 @@ import { */ import { useBlockElement } from '../block-list/use-block-props/use-block-refs'; import usePopoverScroll from './use-popover-scroll'; -import { rectUnion, getVisibleElementBounds } from '../../utils/dom'; +import { rectUnion, getElementBounds } from '../../utils/dom'; const MAX_POPOVER_RECOMPUTE_COUNTER = Number.MAX_SAFE_INTEGER; @@ -90,10 +90,10 @@ function BlockPopover( getBoundingClientRect() { return lastSelectedElement ? rectUnion( - getVisibleElementBounds( selectedElement ), - getVisibleElementBounds( lastSelectedElement ) + getElementBounds( selectedElement ), + getElementBounds( lastSelectedElement ) ) - : getVisibleElementBounds( selectedElement ); + : getElementBounds( selectedElement ); }, contextElement: selectedElement, }; diff --git a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js index 6d64f5a5882cb8..df016e73c29d45 100644 --- a/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js +++ b/packages/block-editor/src/components/block-tools/use-block-toolbar-popover-props.js @@ -17,7 +17,7 @@ import { import { store as blockEditorStore } from '../../store'; import { useBlockElement } from '../block-list/use-block-props/use-block-refs'; import { hasStickyOrFixedPositionValue } from '../../hooks/position'; -import { getVisibleElementBounds } from '../../utils/dom'; +import { getElementBounds } from '../../utils/dom'; const COMMON_PROPS = { placement: 'top-start', @@ -68,7 +68,7 @@ function getProps( // Get how far the content area has been scrolled. const scrollTop = scrollContainer?.scrollTop || 0; - const blockRect = getVisibleElementBounds( selectedBlockElement ); + const blockRect = getElementBounds( selectedBlockElement ); const contentRect = contentElement.getBoundingClientRect(); // Get the vertical position of top of the visible content area. diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index e30f809e387797..6d55f2468d24b6 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -134,23 +134,21 @@ function isScrollable( element ) { ); } +export const WITH_OVERFLOW_ELEMENT_BLOCKS = [ 'core/navigation' ]; /** - * Returns the rect of the element including all visible nested elements. - * - * Visible nested elements, including elements that overflow the parent, are - * taken into account. - * - * This function is useful for calculating the visible area of a block that - * contains nested elements that overflow the block, e.g. the Navigation block, - * which can contain overflowing Submenu blocks. + * Returns the bounding rectangle of an element, with special handling for blocks + * that have visible overflowing children (defined in WITH_OVERFLOW_ELEMENT_BLOCKS). * + * For blocks like Navigation that can have overflowing elements (e.g. submenus), + * this function calculates the combined bounds of both the parent and its visible + * children. The returned rect may extend beyond the viewport. * The returned rect represents the full extent of the element and its visible * children, which may extend beyond the viewport. * * @param {Element} element Element. * @return {DOMRect} Bounding client rect of the element and its visible children. */ -export function getVisibleElementBounds( element ) { +export function getElementBounds( element ) { const viewport = element.ownerDocument.defaultView; if ( ! viewport ) { @@ -158,17 +156,25 @@ export function getVisibleElementBounds( element ) { } let bounds = element.getBoundingClientRect(); - const stack = [ element ]; - let currentElement; - - while ( ( currentElement = stack.pop() ) ) { - // Children won’t affect bounds unless the element is not scrollable. - if ( ! isScrollable( currentElement ) ) { - for ( const child of currentElement.children ) { - if ( isElementVisible( child ) ) { - const childBounds = child.getBoundingClientRect(); - bounds = rectUnion( bounds, childBounds ); - stack.push( child ); + const dataType = element.getAttribute( 'data-type' ); + + /* + * For blocks with overflowing elements (like Navigation), include the bounds + * of visible children that extend beyond the parent container. + */ + if ( dataType && WITH_OVERFLOW_ELEMENT_BLOCKS.includes( dataType ) ) { + const stack = [ element ]; + let currentElement; + + while ( ( currentElement = stack.pop() ) ) { + // Children won’t affect bounds unless the element is not scrollable. + if ( ! isScrollable( currentElement ) ) { + for ( const child of currentElement.children ) { + if ( isElementVisible( child ) ) { + const childBounds = child.getBoundingClientRect(); + bounds = rectUnion( bounds, childBounds ); + stack.push( child ); + } } } } diff --git a/packages/block-editor/src/utils/test/dom.js b/packages/block-editor/src/utils/test/dom.js new file mode 100644 index 00000000000000..50a16a27cf9617 --- /dev/null +++ b/packages/block-editor/src/utils/test/dom.js @@ -0,0 +1,224 @@ +/** + * Internal dependencies + */ +import { getElementBounds, WITH_OVERFLOW_ELEMENT_BLOCKS } from '../dom'; +describe( 'dom', () => { + describe( 'getElementBounds', () => { + it( 'should return a DOMRectReadOnly object if the viewport is not available', () => { + const element = { + ownerDocument: { + defaultView: null, + }, + }; + expect( getElementBounds( element ) ).toEqual( + new window.DOMRectReadOnly() + ); + } ); + it( 'should return a DOMRectReadOnly object if the viewport is available', () => { + const element = { + ownerDocument: { + defaultView: { + getComputedStyle: () => ( { + display: 'block', + visibility: 'visible', + opacity: '1', + } ), + }, + }, + getBoundingClientRect: () => ( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + } ), + getAttribute: ( x ) => x, + }; + expect( getElementBounds( element ) ).toEqual( + new window.DOMRectReadOnly( 0, 0, 100, 100 ) + ); + } ); + it( 'should clip left and right values when an element is larger than the viewport width', () => { + const element = window.document.createElement( 'div' ); + element.getBoundingClientRect = jest.fn().mockReturnValue( { + left: -10, + top: 0, + right: window.innerWidth + 10, + bottom: 100, + width: window.innerWidth, + height: 100, + } ); + expect( getElementBounds( element ).toJSON() ).toEqual( { + left: 0, // Reset to min left bound. + top: 0, + right: window.innerWidth, // Reset to max right bound. + bottom: 100, + width: window.innerWidth, + height: 100, + x: 0, + y: 0, + } ); + } ); + it( 'should return the parent DOMRectReadOnly object if the parent block type is not supported', () => { + const element = window.document.createElement( 'div' ); + element.getBoundingClientRect = jest.fn().mockReturnValue( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + } ); + element.setAttribute( 'data-type', 'test' ); + const childElement = window.document.createElement( 'div' ); + childElement.getBoundingClientRect = jest.fn().mockReturnValue( { + left: 0, + top: 0, + right: 333, + bottom: 333, + width: 333, + height: 333, + x: 0, + y: 0, + } ); + element.appendChild( childElement ); + + expect( getElementBounds( element ).toJSON() ).toEqual( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + x: 0, + y: 0, + } ); + } ); + describe( 'With known block type', () => { + it( 'should return the child DOMRectReadOnly object if it is visible and a known block type', () => { + const element = window.document.createElement( 'div' ); + element.getBoundingClientRect = jest.fn().mockReturnValue( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + } ); + element.setAttribute( + 'data-type', + WITH_OVERFLOW_ELEMENT_BLOCKS[ 0 ] + ); + const childElement = window.document.createElement( 'div' ); + childElement.getBoundingClientRect = jest + .fn() + .mockReturnValue( { + left: 0, + top: 0, + right: 333, + bottom: 333, + width: 333, + height: 333, + x: 0, + y: 0, + } ); + element.appendChild( childElement ); + + expect( getElementBounds( element ).toJSON() ).toEqual( { + left: 0, + top: 0, + right: 333, + bottom: 333, + width: 333, + height: 333, + x: 0, + y: 0, + } ); + } ); + it( 'should return the parent DOMRectReadOnly if the child is scrollable', () => { + const element = window.document.createElement( 'div' ); + element.setAttribute( + 'data-type', + WITH_OVERFLOW_ELEMENT_BLOCKS[ 0 ] + ); + element.style.overflowX = 'auto'; + element.style.overflowY = 'auto'; + element.getBoundingClientRect = jest.fn().mockReturnValue( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + } ); + const childElement = window.document.createElement( 'div' ); + childElement.getBoundingClientRect = jest + .fn() + .mockReturnValue( { + left: 0, + top: 0, + right: 333, + bottom: 333, + width: 333, + height: 333, + x: 0, + y: 0, + } ); + element.appendChild( childElement ); + + expect( getElementBounds( element ).toJSON() ).toEqual( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + x: 0, + y: 0, + } ); + } ); + it( 'should return the parent DOMRectReadOnly object if the child element is not visible', () => { + const element = window.document.createElement( 'div' ); + element.getBoundingClientRect = jest.fn().mockReturnValue( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + } ); + element.setAttribute( + 'data-type', + WITH_OVERFLOW_ELEMENT_BLOCKS[ 0 ] + ); + const childElement = window.document.createElement( 'div' ); + childElement.getBoundingClientRect = jest + .fn() + .mockReturnValue( { + left: 0, + top: 0, + right: 333, + bottom: 333, + width: 333, + height: 333, + x: 0, + y: 0, + } ); + childElement.style.display = 'none'; + element.appendChild( childElement ); + + expect( getElementBounds( element ).toJSON() ).toEqual( { + left: 0, + top: 0, + right: 100, + bottom: 100, + width: 100, + height: 100, + x: 0, + y: 0, + } ); + } ); + } ); + } ); +} ); From adf7e26799b9bbbb8a237df134ca979dd8e1b0e3 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:22:36 +0900 Subject: [PATCH 002/605] Shadow panel: Make the delete modal text translatable (#66712) Co-authored-by: t-hamano Co-authored-by: up1512001 Co-authored-by: mikachan --- .../src/components/global-styles/shadows-edit-panel.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js index 127480ee5af497..f26a8a5ed17436 100644 --- a/packages/edit-site/src/components/global-styles/shadows-edit-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-edit-panel.js @@ -215,8 +215,10 @@ export default function ShadowsEditPanel() { size="medium" > { sprintf( - // translators: %s: name of the shadow - 'Are you sure you want to delete "%s"?', + /* translators: %s: Name of the shadow preset. */ + __( + 'Are you sure you want to delete "%s" shadow preset?' + ), selectedShadow.name ) } From 25849be0c7f6b6b23c3eb09ec6b686e122e92ebd Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:24:50 +0900 Subject: [PATCH 003/605] ComplementaryArea: Fix button position (#66677) Co-authored-by: t-hamano Co-authored-by: up1512001 Co-authored-by: ntsekouras --- .../src/components/complementary-area-header/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interface/src/components/complementary-area-header/style.scss b/packages/interface/src/components/complementary-area-header/style.scss index ce602f369d2aed..c62fe7dc941760 100644 --- a/packages/interface/src/components/complementary-area-header/style.scss +++ b/packages/interface/src/components/complementary-area-header/style.scss @@ -4,6 +4,6 @@ gap: $grid-unit-10; // Always ensure space between contents and close buttons. .interface-complementary-area-header__title { - margin: 0; + margin: 0 auto 0 0; } } From 630ceb42b1c91de8a3b531a09b27a1379cb2c9fd Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 5 Nov 2024 16:08:09 +1100 Subject: [PATCH 004/605] Add "show template" to preview dropdown. (#66514) Co-authored-by: tellthemachines Co-authored-by: andrewserong Co-authored-by: t-hamano Co-authored-by: jasmussen Co-authored-by: paaljoachim Co-authored-by: jameskoster Co-authored-by: stokesman --- .../src/components/document-bar/index.js | 11 +++- .../src/components/document-bar/style.scss | 13 ++++ .../src/components/preview-dropdown/index.js | 61 ++++++++++++++----- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/packages/editor/src/components/document-bar/index.js b/packages/editor/src/components/document-bar/index.js index 031262f52f7268..30990379fe6301 100644 --- a/packages/editor/src/components/document-bar/index.js +++ b/packages/editor/src/components/document-bar/index.js @@ -15,7 +15,7 @@ import { __unstableAnimatePresence as AnimatePresence, } from '@wordpress/components'; import { BlockIcon } from '@wordpress/block-editor'; -import { chevronLeftSmall, chevronRightSmall } from '@wordpress/icons'; +import { chevronLeftSmall, chevronRightSmall, layout } from '@wordpress/icons'; import { displayShortcut } from '@wordpress/keycodes'; import { store as coreStore } from '@wordpress/core-data'; import { store as commandsStore } from '@wordpress/commands'; @@ -59,12 +59,14 @@ export default function DocumentBar( props ) { isNotFound, templateTitle, onNavigateToPreviousEntityRecord, + isTemplatePreview, } = useSelect( ( select ) => { const { getCurrentPostType, getCurrentPostId, getEditorSettings, __experimentalGetTemplateInfo: getTemplateInfo, + getRenderingMode, } = select( editorStore ); const { getEditedEntityRecord, @@ -96,6 +98,7 @@ export default function DocumentBar( props ) { templateTitle: _templateInfo.title, onNavigateToPreviousEntityRecord: getEditorSettings().onNavigateToPreviousEntityRecord, + isTemplatePreview: getRenderingMode() === 'template-locked', }; }, [] ); @@ -146,6 +149,12 @@ export default function DocumentBar( props ) { ) } + { ! isTemplate && isTemplatePreview && ( + + ) } { isNotFound ? ( { __( 'Document not found' ) } ) : ( diff --git a/packages/editor/src/components/document-bar/style.scss b/packages/editor/src/components/document-bar/style.scss index 6a7e7930f5bfb7..749da6ec8c9837 100644 --- a/packages/editor/src/components/document-bar/style.scss +++ b/packages/editor/src/components/document-bar/style.scss @@ -102,3 +102,16 @@ background-color: transparent; } } + +.editor-document-bar__icon-layout.editor-document-bar__icon-layout { + position: absolute; + margin-left: $grid-unit-15; + display: none; + pointer-events: none; + svg { + fill: $gray-600; + } + @include break-small { + display: flex; + } +} diff --git a/packages/editor/src/components/preview-dropdown/index.js b/packages/editor/src/components/preview-dropdown/index.js index e7a19f92548f5f..6fa35c673430cc 100644 --- a/packages/editor/src/components/preview-dropdown/index.js +++ b/packages/editor/src/components/preview-dropdown/index.js @@ -16,7 +16,7 @@ import { Icon, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { desktop, mobile, tablet, external } from '@wordpress/icons'; +import { desktop, mobile, tablet, external, check } from '@wordpress/icons'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -31,21 +31,32 @@ import PostPreviewButton from '../post-preview-button'; import { unlock } from '../../lock-unlock'; export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) { - const { deviceType, homeUrl, isTemplate, isViewable, showIconLabels } = - useSelect( ( select ) => { - const { getDeviceType, getCurrentPostType } = select( editorStore ); - const { getEntityRecord, getPostType } = select( coreStore ); - const { get } = select( preferencesStore ); - const _currentPostType = getCurrentPostType(); - return { - deviceType: getDeviceType(), - homeUrl: getEntityRecord( 'root', '__unstableBase' )?.home, - isTemplate: _currentPostType === 'wp_template', - isViewable: getPostType( _currentPostType )?.viewable ?? false, - showIconLabels: get( 'core', 'showIconLabels' ), - }; - }, [] ); - const { setDeviceType } = useDispatch( editorStore ); + const { + deviceType, + homeUrl, + isTemplate, + isViewable, + showIconLabels, + isTemplateHidden, + templateId, + } = useSelect( ( select ) => { + const { getDeviceType, getCurrentPostType, getCurrentTemplateId } = + select( editorStore ); + const { getRenderingMode } = unlock( select( editorStore ) ); + const { getEntityRecord, getPostType } = select( coreStore ); + const { get } = select( preferencesStore ); + const _currentPostType = getCurrentPostType(); + return { + deviceType: getDeviceType(), + homeUrl: getEntityRecord( 'root', '__unstableBase' )?.home, + isTemplate: _currentPostType === 'wp_template', + isViewable: getPostType( _currentPostType )?.viewable ?? false, + showIconLabels: get( 'core', 'showIconLabels' ), + isTemplateHidden: getRenderingMode() === 'post-only', + templateId: getCurrentTemplateId(), + }; + }, [] ); + const { setDeviceType, setRenderingMode } = useDispatch( editorStore ); const { resetZoomLevel } = unlock( useDispatch( blockEditorStore ) ); const handleDevicePreviewChange = ( newDeviceType ) => { @@ -142,6 +153,24 @@ export default function PreviewDropdown( { forceIsAutosaveable, disabled } ) { ) } + { ! isTemplate && !! templateId && ( + + { + setRenderingMode( + isTemplateHidden + ? 'template-locked' + : 'post-only' + ); + } } + > + { __( 'Show template' ) } + + + ) } { isViewable && ( Date: Tue, 5 Nov 2024 07:17:55 +0100 Subject: [PATCH 005/605] Remove unnecessary tooltip from Video block Text tracks button. (#66716) Co-authored-by: afercia Co-authored-by: t-hamano Co-authored-by: Mamaduka --- packages/block-library/src/video/tracks-editor.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/block-library/src/video/tracks-editor.js b/packages/block-library/src/video/tracks-editor.js index 10fb241a9ecb30..e23d1c93378a69 100644 --- a/packages/block-library/src/video/tracks-editor.js +++ b/packages/block-library/src/video/tracks-editor.js @@ -204,8 +204,6 @@ export default function TracksEditor( { tracks = [], onChange } ) { renderToggle={ ( { isOpen, onToggle } ) => ( Date: Tue, 5 Nov 2024 08:16:21 +0100 Subject: [PATCH 006/605] Docs: Include a note about supported licenses in WordPress packages (#66562) * Docs: Include a note about supported licenses in WordPress packages * Update README.md * Update packages/README.md Co-authored-by: Pascal Birchler --------- Co-authored-by: Pascal Birchler Co-authored-by: gziolo Co-authored-by: swissspidy Co-authored-by: sirreal --- packages/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/README.md b/packages/README.md index cc2f34e38ac055..f73aca35786f5a 100644 --- a/packages/README.md +++ b/packages/README.md @@ -43,9 +43,9 @@ When creating a new package, you need to provide at least the following. Package This assumes that your code is located in the `src` folder and will be transpiled with `Babel`. - For packages that should ship as a WordPress script, include `wpScript: true` in the `package.json` file. This tells the build system to bundle the package for use as a WordPress script. + For production packages that will ship as a WordPress script, include `wpScript: true` in the `package.json` file. This tells the build system to bundle the package for use as a WordPress script. - For packages that should ship as a WordPress script module, include a `wpScriptModuleExports` field the `package.json` file. The value of this field can be a string to expose a single script module, or an object with a [shape like the standard `exports` object](https://nodejs.org/docs/latest-v20.x/api/packages.html#subpath-exports) to expose multiple script modules from a single package: + For production packages that will ship as a WordPress script module, include a `wpScriptModuleExports` field in the `package.json` file. The value of this field can be a string to expose a single script module, or an object with a [shape like the standard `exports` object](https://nodejs.org/docs/latest-v20.x/api/packages.html#subpath-exports) to expose multiple script modules from a single package: ```jsonc { @@ -64,7 +64,7 @@ When creating a new package, you need to provide at least the following. Package } ``` - Both `wpScript` and `wpScriptModuleExports` may be included if the package exposes both a script and a script module. + Both `wpScript` and `wpScriptModuleExports` may be included if the package exposes both a script and a script module. These fields are also essential when performing a license check for all their dependencies, because they trigger strict validation against compatibility with GPL v2. All remaining dependencies WordPress doesn't distribute but uses for development purposes can contain also a few other OSS compatible licenses. 1. `README.md` file containing at least: - Package name From adaeff3cb7d8ddddcaf2d73fe94779a262962c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:39:10 +0100 Subject: [PATCH 007/605] Storybook: fix Dataviews action modals (#66727) Co-authored-by: oandregal Co-authored-by: ntsekouras --- storybook/package-styles/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storybook/package-styles/config.js b/storybook/package-styles/config.js index 21215fcad5c21e..d3614a0594b45c 100644 --- a/storybook/package-styles/config.js +++ b/storybook/package-styles/config.js @@ -55,8 +55,8 @@ const CONFIG = [ }, { componentIdMatcher: /^dataviews-/, - ltr: [ dataviewsLtr, componentsLtr ], - rtl: [ dataviewsRtl, componentsRtl ], + ltr: [ componentsLtr, dataviewsLtr ], + rtl: [ componentsRtl, dataviewsRtl ], }, ]; From 6270af662a8c785686028ee4373547e82f7e5e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:47:06 +0100 Subject: [PATCH 008/605] Documentation: reorganize to bootstrap DataForm API section (#66729) --- packages/dataviews/README.md | 163 +++++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 67 deletions(-) diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 95b8fc898555c3..066a32fc40f47a 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -1,10 +1,9 @@ -# DataViews +# The `@wordpress/dataviews` package -DataViews is a component that provides an API to render datasets using different types of layouts (table, grid, list, etc.). +The DataViews package offers two components to work with a given dataset: -DataViews is data agnostic, it can work with data coming from a static (JSON file) or dynamic source (HTTP Request) — it just requires the data to be an array of objects that have an unique identifier. Consumers are responsible to query the data source appropiately based on the DataViews props: - -![DataViews flow](https://developer.wordpress.org/files/2024/09/368600071-20aa078f-7c3d-406d-8dd0-8b764addd22a.png "DataViews flow") +- `DataViews`: allows rendering a dataset using different types of layouts (table, grid, list) and interaction capabilities (search, filters, sorting, etc.). +- `DataForm`: allows editing the items from the same dataset. ## Installation @@ -14,7 +13,13 @@ Install the module npm install @wordpress/dataviews --save ``` -## Usage +## `DataViews` + +### Usage + +The component is data agnostic, it just requires the data to be an array of objects with an unique identifier — it can work with data coming from a static (e.g.: JSON file) or dynamic source (e.g.: HTTP Request). Consumers are responsible to query the data source appropiately: + +![DataViews flow](https://developer.wordpress.org/files/2024/09/368600071-20aa078f-7c3d-406d-8dd0-8b764addd22a.png "DataViews flow") ```jsx const Example = () => { @@ -34,11 +39,11 @@ const Example = () => { }; ``` - + -## Properties +### Properties -### `data`: `Object[]` +#### `data`: `Object[]` The dataset to work with, represented as a one-dimensional array. @@ -60,7 +65,7 @@ const data = [ By default, dataviews would use each record's `id` as an unique identifier. If it's not, the consumer should provide a `getItemId` function that returns one. -### `fields`: `Object[]` +#### `fields`: `Object[]` The fields describe the visible items for each record in the dataset. @@ -140,7 +145,7 @@ Each field is an object with the following properties: - `operators`: the list of [operators](#operators) supported by the field. - `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. -### `view`: `object` +#### `view`: `object` The view object configures how the dataset is visible to the user. @@ -183,7 +188,24 @@ Properties: - `fields`: the `id` of the fields that are visible in the UI and the specific order in which they are displayed. - `layout`: config that is specific to a particular layout type. -#### Properties of `layout` +##### Filter operators + +Allowed operators: + +| Operator | Selection | Description | Example | +| ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | +| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin | +| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin | +| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor | +| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor | +| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | +| `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | + +`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. + +> The legacy operators `in` and `notIn` have been deprecated and will be removed soon. In the meantime, they work as `is` and `isNot` operators, respectively. + +##### Properties of `layout` | Properties of `layout` | Table | Grid | List | | --------------------------------------------------------------------------------------------------------------- | ----- | ---- | ---- | @@ -194,7 +216,36 @@ Properties: | `combinedFields`: a list of "virtual" fields that are made by combining others. See "Combining fields" section. | ✓ | | | | `styles`: additional `width`, `maxWidth`, `minWidth` styles for each field column. | ✓ | | | -### `onChangeView`: `function` +##### Combining fields + +The `table` layout has the ability to create "virtual" fields that are made out by combining existing ones. + +Each "virtual field", has to provide an `id` and `label` (optionally a `header` instead), which have the same meaning as any other field. + +Additionally, they need to provide: + +- `children`: a list of field's `id` to combine +- `direction`: how should they be stacked, `vertical` or `horizontal` + +For example, this is how you'd define a `site` field which is a combination of a `title` and `description` fields, which are not displayed: + +```js +{ + fields: [ 'site', 'status' ], + layout: { + combinedFields: [ + { + id: 'site', + label: 'Site', + children: [ 'title', 'description' ], + direction: 'vertical', + } + ] + } +} +``` + +#### `onChangeView`: `function` The view is a representation of the visible state of the dataset: what type of layout is used to display it (table, grid, etc.), how the dataset is filtered, how it is sorted or paginated. @@ -259,7 +310,7 @@ function MyCustomPageTable() { } ``` -### `actions`: `Object[]` +#### `actions`: `Object[]` Collection of operations that can be performed upon each record. @@ -277,28 +328,28 @@ Each action is an object with the following properties: - `supportsBulk`: Whether the action can be used as a bulk action. False by default. - `disabled`: Whether the action is disabled. False by default. -### `paginationInfo`: `Object` +#### `paginationInfo`: `Object` - `totalItems`: the total number of items in the datasets. - `totalPages`: the total number of pages, taking into account the total items in the dataset and the number of items per page provided by the user. -### `search`: `boolean` +#### `search`: `boolean` Whether the search input is enabled. `true` by default. -### `searchLabel`: `string` +#### `searchLabel`: `string` What text to show in the search input. "Search" by default. -### `getItemId`: `function` +#### `getItemId`: `function` Function that receives an item and returns an unique identifier for it. By default, it uses the `id` of the item as unique identifier. If it's not, the consumer should provide their own. -### `isLoading`: `boolean` +#### `isLoading`: `boolean` Whether the data is loading. `false` by default. -### `defaultLayouts`: `Record< string, view >` +#### `defaultLayouts`: `Record< string, view >` This property provides layout information about the view types that are active. If empty, enables all layout types (see "Layout Types") with empty layout data. @@ -316,67 +367,45 @@ const defaultLayouts = { The `defaultLayouts` property should be an object that includes properties named `table`, `grid`, or `list`. Each of these properties should contain a `layout` property, which holds the configuration for each specific layout type. Check [here](#properties-of-layout) the full list of properties available for each layout's configuration -### `onChangeSelection`: `function` +#### `onChangeSelection`: `function` Callback that signals the user selected one of more items, and takes them as parameter. So far, only the `list` view implements it. -## Types - -### Layouts - -- `table`: the view uses a table layout. -- `grid`: the view uses a grid layout. -- `list`: the view uses a list layout. - -### Fields - -> The `enumeration` type was removed as it was deemed redundant with the field.elements metadata. New types will be introduced soon. +## `DataForm` -## Combining fields +### Usage -The `table` layout has the ability to create "virtual" fields that are made out by combining existing ones. - -Each "virtual field", has to provide an `id` and `label` (optionally a `header` instead), which have the same meaning as any other field. +```jsx +const Example = () => { + // Declare data, fields, etc. -Additionally, they need to provide: + return ( + + ) +} +``` -- `children`: a list of field's `id` to combine -- `direction`: how should they be stacked, `vertical` or `horizontal` + -For example, this is how you'd define a `site` field which is a combination of a `title` and `description` fields, which are not displayed: +### Props -```js -{ - fields: [ 'site', 'status' ], - layout: { - combinedFields: [ - { - id: 'site', - label: 'Site', - children: [ 'title', 'description' ], - direction: 'vertical', - } - ] - } -} -``` +#### `data`: `Object[]` -### Operators +Same as `data` property of `DataViews`. -Allowed operators: +#### `fields`: `Object[]` -| Operator | Selection | Description | Example | -| ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | -| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin | -| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin | -| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor | -| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor | -| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | -| `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | +Same as `fields` property of `DataViews`. -`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. +#### `form`: `Object[]` -> The legacy operators `in` and `notIn` have been deprecated and will be removed soon. In the meantime, they work as `is` and `isNot` operators, respectively. +- `type`: either `regular` or `panel`. +- `fields`: a list of fields ids that should be rendered. ## Contributing to this package From 2da866b34525bb49fa79a21176dc95c9525e68d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:16:15 +0100 Subject: [PATCH 009/605] Document `filterSortAndPaginate` & `isItemValid` utilities (#66738) --- packages/dataviews/README.md | 31 ++++++++++++++++++++++++++++ packages/dataviews/src/validation.ts | 9 ++++++++ 2 files changed, 40 insertions(+) diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 066a32fc40f47a..79875542378b3e 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -407,6 +407,37 @@ Same as `fields` property of `DataViews`. - `type`: either `regular` or `panel`. - `fields`: a list of fields ids that should be rendered. +## Utilities + +### `filterSortAndPaginate` + +Utility to apply the view config (filters, search, sorting, and pagination) to a dataset client-side. + +Parameters: + +- `data`: the dataset, as described in the "data" property of DataViews. +- `view`: the view config, as described in the "view" property of DataViews. +- `fields`: the fields config, as described in the "fields" property of DataViews. + +Returns an object containing: + +- `data`: the new dataset, with the view config applied. +- `paginationInfo`: object containing the following properties: + - `totalItems`: total number of items for the current view config. + - `totalPages`: total number of pages for the current view config. + +### `isItemValid` + +Utility to determine whether or not the given item's value is valid according to the current fields and form config. + +Parameters: + +- `item`: the item, as described in the "data" property of DataForm. +- `fields`: the fields config, as described in the "fields" property of DataForm. +- `form`: the form config, as described in the "form" property of DataForm. + +Returns a boolean indicating if the item is valid (true) or not (false). + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. diff --git a/packages/dataviews/src/validation.ts b/packages/dataviews/src/validation.ts index 41969a7960af65..0a6542da4e8d40 100644 --- a/packages/dataviews/src/validation.ts +++ b/packages/dataviews/src/validation.ts @@ -4,6 +4,15 @@ import { normalizeFields } from './normalize-fields'; import type { Field, Form } from './types'; +/** + * Whether or not the given item's value is valid according to the fields and form config. + * + * @param item The item to validate. + * @param fields Fields config. + * @param form Form config. + * + * @return A boolean indicating if the item is valid (true) or not (false). + */ export function isItemValid< Item >( item: Item, fields: Field< Item >[], From 3dece5ef458978a018f4280abfaa4e39ad92e554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:20:30 +0100 Subject: [PATCH 010/605] Documentation: move docs for filters to proper place (#66743) --- packages/dataviews/README.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 79875542378b3e..00ab60ff5d0474 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -142,9 +142,22 @@ Each field is an object with the following properties: - `enableHiding`: whether the field can be hidden. True by default. - `enableGlobalSearch`: whether the field is searchable. False by default. - `filterBy`: configuration for the filters enabled by the `elements` property. - - `operators`: the list of [operators](#operators) supported by the field. + - `operators`: the list of operators supported by the field. - `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. +##### Filter operators + +| Operator | Selection | Description | Example | +| ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | +| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin | +| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin | +| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor | +| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor | +| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | +| `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | + +`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. + #### `view`: `object` The view object configures how the dataset is visible to the user. @@ -188,23 +201,6 @@ Properties: - `fields`: the `id` of the fields that are visible in the UI and the specific order in which they are displayed. - `layout`: config that is specific to a particular layout type. -##### Filter operators - -Allowed operators: - -| Operator | Selection | Description | Example | -| ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | -| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin | -| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin | -| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor | -| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor | -| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | -| `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | - -`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. - -> The legacy operators `in` and `notIn` have been deprecated and will be removed soon. In the meantime, they work as `is` and `isNot` operators, respectively. - ##### Properties of `layout` | Properties of `layout` | Table | Grid | List | From 0c869d4b4d2272efdeb37297051876043c2ed556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:47:01 +0100 Subject: [PATCH 011/605] Documentation: add missing properties for DataViews/DataForm components (#66749) --- packages/dataviews/README.md | 117 +++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 27 deletions(-) diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 00ab60ff5d0474..d61df206409152 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -63,7 +63,7 @@ const data = [ ]; ``` -By default, dataviews would use each record's `id` as an unique identifier. If it's not, the consumer should provide a `getItemId` function that returns one. +By default, dataviews would use each record's `id` as an unique identifier. If the records don't have a `id` property that identify them uniquely, they consumer needs to provide a `getItemId` function that returns an unique identifier for the record. #### `fields`: `Object[]` @@ -109,8 +109,8 @@ const fields = [ enableSorting: false, }, { - label: __( 'Status' ), id: 'status', + label: __( 'Status' ), getValue: ( { item } ) => STATUSES.find( ( { value } ) => value === item.status )?.label ?? item.status, @@ -126,27 +126,35 @@ const fields = [ Each field is an object with the following properties: - `id`: identifier for the field. Unique. +- `type`: the type of the field. See "Field types" section. - `label`: the field's name to be shown in the UI. +- `header`: defaults to the label. Allows providing a React element to render the field labels. - `getValue`: function that returns the value of the field, defaults to `field[id]`. - `render`: function that renders the field. Optional, `getValue` will be used if `render` is not defined. -- elements: The list of options to pick from when using the field as a filter or when editing (DataForm component). It expects an array of objects with the following properties: - - - `value`: The id of the value to filter to (for internal use) - - `label`: The text that will be displayed in the UI for the item. - - `description`: A longer description that describes the element, to also be displayed. Optional. - - To enable the filter by a field we just need to set a proper value to the `elements` property of the field we'd like to filter by. - -- `type`: the type of the field. See "Field types". +- `Edit`: function that renders an edit control for the field. Alternatively, can provide a string declaring one of default controls provided by DataForm `text`, `integer`, `datetime`, `radio`, `select`. +- `sort`: a compare function that determines the order of the records. +- `isValid`: callback to decide if a field should be displayed. +- `isVisible`: callback to decide if a field should be visible. - `enableSorting`: whether the data can be sorted by the given field. True by default. - `enableHiding`: whether the field can be hidden. True by default. - `enableGlobalSearch`: whether the field is searchable. False by default. +- elements: The list of options to pick from when using the field as a filter or when editing (DataForm component). Providing a list of to the field automatically creates a filter for it. It expects an array of objects with the following properties: + - `value`: The id of the value to filter to (for internal use) + - `label`: The text that will be displayed in the UI for the item. + - `description`: A longer description that describes the element, to also be displayed. Optional. - `filterBy`: configuration for the filters enabled by the `elements` property. - - `operators`: the list of operators supported by the field. + - `operators`: the list of operators supported by the field. See "Filter operators" below. - `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. +##### Field types + +Current supported types include: `text`, `integer`, `datetime`. + +If a field declares a `type` the `sort`, `isValid`, and `Edit` functions will be provided with default implementations. They will overriden if the field provides its own. + ##### Filter operators + | Operator | Selection | Description | Example | | ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | | `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin | @@ -158,7 +166,7 @@ Each field is an object with the following properties: `is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. -#### `view`: `object` +#### `view`: `Object` The view object configures how the dataset is visible to the user. @@ -198,7 +206,7 @@ Properties: - `field`: the field used for sorting the dataset. - `direction`: the direction to use for sorting, one of `asc` or `desc`. -- `fields`: the `id` of the fields that are visible in the UI and the specific order in which they are displayed. +- `fields`: a list of field `id` that are visible in the UI and the specific order in which they are displayed. - `layout`: config that is specific to a particular layout type. ##### Properties of `layout` @@ -243,9 +251,9 @@ For example, this is how you'd define a `site` field which is a combination of a #### `onChangeView`: `function` -The view is a representation of the visible state of the dataset: what type of layout is used to display it (table, grid, etc.), how the dataset is filtered, how it is sorted or paginated. +Callback executed when the view has changed. It receives the new view object as a parameter. -It's the consumer's responsibility to work with the data provider to make sure the user options defined through the view's config (sort, pagination, filters, etc.) are respected. The `onChangeView` prop allows the consumer to provide a callback to be called when the view config changes, to process the data accordingly. +The view is a representation of the visible state of the dataset: what type of layout is used to display it (table, grid, etc.), how the dataset is filtered, how it is sorted or paginated. It's the consumer's responsibility to use the view config to query the data provider and make sure the user decisions (sort, pagination, filters, etc.) are respected. The following example shows how a view object is used to query the WordPress REST API via the entities abstraction. The same can be done with any other data provider. @@ -313,16 +321,18 @@ Collection of operations that can be performed upon each record. Each action is an object with the following properties: - `id`: string, required. Unique identifier of the action. For example, `move-to-trash`. -- `label`: string|function, required. User facing description of the action. For example, `Move to Trash`. In case we want to adjust the label based on the selected items, a function which accepts the selected records as input can be provided. This function should always return a `string` value. +- `label`: string|function, required. User facing description of the action. For example, `Move to Trash`. It can also take a function that takes the selected items as a parameter and returns a string: this can be useful to provide a dynamic label based on the selection. - `isPrimary`: boolean, optional. Whether the action should be listed inline (primary) or in hidden in the more actions menu (secondary). -- `icon`: icon to show for primary actions. It's required for a primary action, otherwise the action would be considered secondary. +- `icon`: SVG element. Icon to show for primary actions. It's required for a primary action, otherwise the action would be considered secondary. - `isEligible`: function, optional. Whether the action can be performed for a given record. If not present, the action is considered to be eligible for all items. It takes the given record as input. - `isDestructive`: boolean, optional. Whether the action can delete data, in which case the UI would communicate it via red color. -- `callback`: function, required unless `RenderModal` is provided. Callback function that takes the record as input and performs the required action. -- `RenderModal`: ReactElement, optional. If an action requires that some UI be rendered in a modal, it can provide a component which takes as props the record as `item` and a `closeModal` function. When this prop is provided, the `callback` property is ignored. -- `hideModalHeader`: boolean, optional. This property is used in combination with `RenderModal` and controls the visibility of the modal's header. If the action renders a modal and doesn't hide the header, the action's label is going to be used in the modal's header. - `supportsBulk`: Whether the action can be used as a bulk action. False by default. - `disabled`: Whether the action is disabled. False by default. +- `context`: where this action would be visible. One of `list`, `single`. +- `callback`: function, required unless `RenderModal` is provided. Callback function that takes as input the list of items to operate with, and performs the required action. +- `RenderModal`: ReactElement, optional. If an action requires that some UI be rendered in a modal, it can provide a component which takes as input the the list of `items` to operate with, `closeModal` function, and `onActionPerformed` function. When this prop is provided, the `callback` property is ignored. +- `hideModalHeader`: boolean, optional. This property is used in combination with `RenderModal` and controls the visibility of the modal's header. If the action renders a modal and doesn't hide the header, the action's label is going to be used in the modal's header. +- `modalHeader`: string, optional. The header of the modal. #### `paginationInfo`: `Object` @@ -339,7 +349,9 @@ What text to show in the search input. "Search" by default. #### `getItemId`: `function` -Function that receives an item and returns an unique identifier for it. By default, it uses the `id` of the item as unique identifier. If it's not, the consumer should provide their own. +Function that receives an item and returns an unique identifier for it. + +By default, dataviews would use each record's `id` as an unique identifier. If the records don't have a `id` property that identify them uniquely, they consumer needs to provide a `getItemId` function that returns an unique identifier for the record. #### `isLoading`: `boolean` @@ -361,11 +373,23 @@ const defaultLayouts = { }; ``` -The `defaultLayouts` property should be an object that includes properties named `table`, `grid`, or `list`. Each of these properties should contain a `layout` property, which holds the configuration for each specific layout type. Check [here](#properties-of-layout) the full list of properties available for each layout's configuration +The `defaultLayouts` property should be an object that includes properties named `table`, `grid`, or `list`. Each of these properties should contain a `layout` property, which holds the configuration for each specific layout type. Check "Properties of layout" for the full list of properties available for each layout's configuration + +#### `selection`: `string[]` + +The list of selected items' ids. + +If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves as a controlled component, otherwise, it behaves like an uncontrolled component. #### `onChangeSelection`: `function` -Callback that signals the user selected one of more items, and takes them as parameter. So far, only the `list` view implements it. +Callback that signals the user selected one of more items. It receives the list of selected items' ids as a parameter. + +If `selection` and `onChangeSelection` are provided, the `DataViews` component behaves as a controlled component, otherwise, it behaves like an uncontrolled component. + +#### `header`: React component + +React component to be rendered next to the view config button. ## `DataForm` @@ -388,11 +412,13 @@ const Example = () => { -### Props +### Properties -#### `data`: `Object[]` +#### `data`: `Object` + +A single item to be edited. -Same as `data` property of `DataViews`. +This could be thought as as a single record coming from the `data` property of `DataViews` — though it doesn't need to be; it can be, for example, a mix of records if you support bulk editing. #### `fields`: `Object[]` @@ -403,6 +429,43 @@ Same as `fields` property of `DataViews`. - `type`: either `regular` or `panel`. - `fields`: a list of fields ids that should be rendered. +#### `onChange`: `function` + +Callback function that receives an object with the edits done by the user. + +Example: + +```js +const data = { + id: 1, + title: 'Title', + author: 'Admin', + date: '2012-04-23T18:25:43.511Z', +}; + +const onChange = ( edits ) => { + /* + * edits will contain user edits. + * For example, if the user edited the title + * edits will be: + * + * { + * title: 'New title' + * } + * + */ +}; + +return ( + +); +``` + ## Utilities ### `filterSortAndPaginate` From 6cfeadc0d3a5361307877b095fdb981335dac4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Tue, 5 Nov 2024 15:06:35 +0100 Subject: [PATCH 012/605] Tools: Enforce the same order of fields in `package.json` files (#66239) * Update package managing flow * Adapt packages webpack config for workspaces * Ignore "dev" scripts this should be done another way * Annotate packages included as WordPress scripts * Do not annotate react-native packages * Update packages webpack to use wpScript package.json * Implement Gutenberg specific check-licenses script * Remove redundant entries from the ignore list * Update some docs langauge about lerna and npm workspaces * Tools: Enforce the same order of fields in `package.json` files * Update the PR number in the changelog entry * Update packages/npm-package-json-lint-config/index.js Co-authored-by: Jon Surrell * Update CHANGELOG.md * Update the order of properties added recently --------- Co-authored-by: Aki Hamano Co-authored-by: Jon Surrell Co-authored-by: t-hamano Co-authored-by: sirreal Co-authored-by: gziolo --- packages/a11y/package.json | 6 +++--- packages/annotations/package.json | 4 ++-- packages/api-fetch/package.json | 4 ++-- packages/autop/package.json | 4 ++-- packages/blob/package.json | 4 ++-- packages/block-directory/package.json | 4 ++-- packages/block-editor/package.json | 4 ++-- packages/block-library/package.json | 14 +++++++------- .../package.json | 4 ++-- .../block-serialization-spec-parser/package.json | 4 ++-- packages/blocks/package.json | 4 ++-- packages/commands/package.json | 4 ++-- packages/components/package.json | 6 +++--- packages/compose/package.json | 4 ++-- packages/core-commands/package.json | 4 ++-- packages/core-data/package.json | 4 ++-- packages/customize-widgets/package.json | 4 ++-- packages/data-controls/package.json | 4 ++-- packages/data/package.json | 4 ++-- packages/dataviews/package.json | 4 ++-- packages/date/package.json | 4 ++-- packages/deprecated/package.json | 4 ++-- packages/dom-ready/package.json | 4 ++-- packages/dom/package.json | 4 ++-- packages/edit-post/package.json | 4 ++-- packages/edit-site/package.json | 4 ++-- packages/edit-widgets/package.json | 4 ++-- packages/editor/package.json | 4 ++-- packages/element/package.json | 4 ++-- packages/escape-html/package.json | 4 ++-- packages/fields/package.json | 4 ++-- packages/format-library/package.json | 4 ++-- packages/hooks/package.json | 4 ++-- packages/html-entities/package.json | 4 ++-- packages/i18n/package.json | 4 ++-- packages/icons/package.json | 6 +++--- packages/interactivity-router/package.json | 2 +- packages/interactivity/package.json | 2 +- packages/interface/package.json | 4 ++-- packages/is-shallow-equal/package.json | 4 ++-- packages/keyboard-shortcuts/package.json | 4 ++-- packages/keycodes/package.json | 4 ++-- packages/list-reusable-blocks/package.json | 4 ++-- packages/media-utils/package.json | 4 ++-- packages/notices/package.json | 4 ++-- packages/npm-package-json-lint-config/CHANGELOG.md | 4 ++++ packages/npm-package-json-lint-config/index.js | 4 ++++ packages/nux/package.json | 4 ++-- packages/patterns/package.json | 4 ++-- packages/plugins/package.json | 4 ++-- packages/preferences-persistence/package.json | 4 ++-- packages/preferences/package.json | 4 ++-- packages/primitives/package.json | 6 +++--- packages/priority-queue/package.json | 4 ++-- packages/private-apis/package.json | 4 ++-- packages/react-i18n/package.json | 4 ++-- packages/redux-routine/package.json | 4 ++-- packages/reusable-blocks/package.json | 4 ++-- packages/rich-text/package.json | 6 +++--- packages/router/package.json | 4 ++-- packages/server-side-render/package.json | 4 ++-- packages/shortcode/package.json | 4 ++-- packages/style-engine/package.json | 4 ++-- packages/sync/package.json | 4 ++-- packages/token-list/package.json | 4 ++-- packages/undo-manager/package.json | 4 ++-- packages/url/package.json | 4 ++-- packages/viewport/package.json | 4 ++-- packages/vips/package.json | 4 ++-- packages/warning/package.json | 4 ++-- packages/widgets/package.json | 4 ++-- packages/wordcount/package.json | 4 ++-- 72 files changed, 156 insertions(+), 148 deletions(-) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 09fa62a9d082b7..008bd9088e0778 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -27,8 +27,9 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "types": "build-types", + "wpScript": true, "wpScriptModuleExports": "./build-module/module/index.js", + "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/dom-ready": "*", @@ -36,6 +37,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 8a94b326a78194..b1d6d210807a87 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/data": "*", @@ -38,6 +39,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 26f156a0aa7d77..7d5b8dfd588897 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", @@ -34,6 +35,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/autop/package.json b/packages/autop/package.json index 2169ff828da47a..336dda06edfe2c 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -32,6 +33,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/blob/package.json b/packages/blob/package.json index 4b04d2ea2ddebd..0dc01ac7198f59 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -32,6 +33,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 59025e1ad45c7a..18a40824aa4754 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/a11y": "*", @@ -54,6 +55,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 2b5cc8d6275e89..26b06474f54b18 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": [ "build-style/**", "src/**/*.scss", @@ -87,6 +88,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 2c880301c8355d..b196e53e5cd0f9 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -25,11 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "sideEffects": [ - "build-style/**", - "src/**/*.scss", - "{src,build,build-module}/*/init.js" - ], + "wpScript": true, "wpScriptModuleExports": { "./file/view": "./build-module/file/view.js", "./image/view": "./build-module/image/view.js", @@ -37,6 +33,11 @@ "./query/view": "./build-module/query/view.js", "./search/view": "./build-module/search/view.js" }, + "sideEffects": [ + "build-style/**", + "src/**/*.scss", + "{src,build,build-module}/*/init.js" + ], "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/a11y": "*", @@ -88,6 +89,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index d31ab188c0b6d6..010132ac971db1 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -33,6 +34,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 7cca4a7ca1a68b..d430d2afeebf5a 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -25,6 +25,7 @@ "npm": ">=8.19.2" }, "main": "parser.js", + "wpScript": true, "sideEffects": false, "dependencies": { "pegjs": "^0.10.0", @@ -37,6 +38,5 @@ "build": "concurrently \"npm run build:js\" \"npm run build:php\"", "build:js": "pegjs --format commonjs -o ./parser.js ./grammar.pegjs", "build:php": "node bin/create-php-parser.js" - }, - "wpScript": true + } } diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 2d944fe656363b..42890e9b7d56bc 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": [ "{src,build,build-module}/{index.js,store/index.js}" ], @@ -62,6 +63,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/commands/package.json b/packages/commands/package.json index 98eadca595ca86..cebf3237a00d75 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/components": "*", @@ -44,6 +45,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/components/package.json b/packages/components/package.json index faf923ad426bf6..6871511cf5b1e5 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -25,11 +25,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, + "types": "build-types", "sideEffects": [ "build-style/**", "src/**/*.scss" ], - "types": "build-types", "dependencies": { "@ariakit/react": "^0.4.10", "@babel/runtime": "7.25.7", @@ -83,6 +84,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/compose/package.json b/packages/compose/package.json index adff24f6663006..68b00b24298d87 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -27,6 +27,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -49,6 +50,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/core-commands/package.json b/packages/core-commands/package.json index ac15b3578cf6ff..db2fb900865c80 100644 --- a/packages/core-commands/package.json +++ b/packages/core-commands/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", @@ -48,6 +49,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 44ed3aaad5dd3a..a7216e931a70ca 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": [ "{src,build,build-module}/index.js" @@ -60,6 +61,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index ef6748f34205e8..41b3a7bd463aa3 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -23,6 +23,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/block-editor": "*", @@ -55,6 +56,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 948efe7fb64022..e4898ff1d0e1ab 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", @@ -38,6 +39,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/data/package.json b/packages/data/package.json index 0e46b1a366918e..fd1ef7ef4d7489 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -50,6 +51,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/dataviews/package.json b/packages/dataviews/package.json index 5599a84b92d34c..df30fea1a1c714 100644 --- a/packages/dataviews/package.json +++ b/packages/dataviews/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -47,6 +48,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/date/package.json b/packages/date/package.json index f53c6314912271..d67c1dc527caee 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", @@ -34,6 +35,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 69d2414b0c11a2..64ffc6cd30b251 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -33,6 +34,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 0202229b202842..6e7986c4965dc6 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -32,6 +33,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/dom/package.json b/packages/dom/package.json index 58c572545cc526..97576e9a22e0ff 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -34,6 +35,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index c594722d180e4f..1b28c1d5f31aa6 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/a11y": "*", @@ -66,6 +67,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 95f9cd5f05742a..67932bcb06321e 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@react-spring/web": "^9.4.5", @@ -82,6 +83,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 0629dca5ec1052..331dbc742dbc8c 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/api-fetch": "*", @@ -63,6 +64,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/editor/package.json b/packages/editor/package.json index 61cce478cf08fe..e7b46685719dd4 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": [ "build-style/**", "src/**/*.scss", @@ -85,6 +86,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/element/package.json b/packages/element/package.json index 3004dadb7ea605..4a196255971cfb 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -40,6 +41,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 72863ce15fd8b8..135876460bd05e 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -32,6 +33,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/fields/package.json b/packages/fields/package.json index e0b7125cf3e4eb..34ef8992f93adf 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": [ "build-style/**", @@ -62,6 +63,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/format-library/package.json b/packages/format-library/package.json index f07ba0f4b61b35..41852910ff5389 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/a11y": "*", @@ -46,6 +47,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/hooks/package.json b/packages/hooks/package.json index d0f2fd381b83ca..857cb24a877468 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -25,12 +25,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7" }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index d717a89bf049b8..4ecab2056c9c3a 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -26,12 +26,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7" }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 33de2873f75a6a..53e81b64a65349 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "bin": { "pot-to-php": "./tools/pot-to-php.js" @@ -39,6 +40,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/icons/package.json b/packages/icons/package.json index 125b6f19beaa02..895f2e184b4e48 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -16,7 +16,6 @@ "url": "git+https://github.com/WordPress/gutenberg.git", "directory": "packages/icons" }, - "sideEffects": false, "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, @@ -27,7 +26,9 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", + "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/element": "*", @@ -35,6 +36,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/interactivity-router/package.json b/packages/interactivity-router/package.json index ce9b268af556ac..378ddda56ee76b 100644 --- a/packages/interactivity-router/package.json +++ b/packages/interactivity-router/package.json @@ -25,8 +25,8 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "types": "build-types", "wpScriptModuleExports": "./build-module/index.js", + "types": "build-types", "dependencies": { "@wordpress/a11y": "*", "@wordpress/interactivity": "*" diff --git a/packages/interactivity/package.json b/packages/interactivity/package.json index 106ed44bf44d39..c9edd586bc9959 100644 --- a/packages/interactivity/package.json +++ b/packages/interactivity/package.json @@ -25,11 +25,11 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", - "types": "build-types", "wpScriptModuleExports": { ".": "./build-module/index.js", "./debug": "./build-module/debug.js" }, + "types": "build-types", "dependencies": { "@preact/signals": "^1.3.0", "preact": "^10.24.2" diff --git a/packages/interface/package.json b/packages/interface/package.json index 807135ae0160a6..f6a7952cb0d051 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": [ "build-style/**", "src/**/*.scss", @@ -52,6 +53,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index 8da2643a58b840..7dafdea88e7a4f 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -34,6 +34,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -41,6 +42,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/keyboard-shortcuts/package.json b/packages/keyboard-shortcuts/package.json index 50ee79d554a89f..ce029907c0dbf4 100644 --- a/packages/keyboard-shortcuts/package.json +++ b/packages/keyboard-shortcuts/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/data": "*", @@ -36,6 +37,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 3e820ef68cf97e..2038db1984760f 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -33,6 +34,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 129909a948ef1d..328695f31925ee 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -25,6 +25,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/api-fetch": "*", @@ -41,6 +42,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index 20eef4dd83c6b4..e57b0f51844504 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -25,6 +25,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", @@ -36,6 +37,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/notices/package.json b/packages/notices/package.json index 34d2253870128b..f150616445b060 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", @@ -36,6 +37,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/npm-package-json-lint-config/CHANGELOG.md b/packages/npm-package-json-lint-config/CHANGELOG.md index 9c0aafedcead1a..9dee828f63fea0 100644 --- a/packages/npm-package-json-lint-config/CHANGELOG.md +++ b/packages/npm-package-json-lint-config/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancement + +- Include `exports`, `wpScript`, `wpScriptModuleExports` and `sideEffects` in the `prefer-property-order` rule ([#66239](https://github.com/WordPress/gutenberg/pull/66239)). + ## 5.11.0 (2024-10-30) ## 5.10.0 (2024-10-16) diff --git a/packages/npm-package-json-lint-config/index.js b/packages/npm-package-json-lint-config/index.js index 831970c59bf3a6..e1abd5e34f99c5 100644 --- a/packages/npm-package-json-lint-config/index.js +++ b/packages/npm-package-json-lint-config/index.js @@ -56,8 +56,12 @@ const defaultConfig = { 'type', 'main', 'module', + 'exports', 'react-native', + 'wpScript', + 'wpScriptModuleExports', 'types', + 'sideEffects', 'bin', 'dependencies', 'devDependencies', diff --git a/packages/nux/package.json b/packages/nux/package.json index ccf1cb774c2424..50a4d5acf6d028 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": [ "build-style/**", "src/**/*.scss", @@ -46,6 +47,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/patterns/package.json b/packages/patterns/package.json index 92ef655dc3c9c7..d29fc9bcf49bca 100644 --- a/packages/patterns/package.json +++ b/packages/patterns/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": [ "build-style/**", "src/**/*.scss", @@ -53,6 +54,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/plugins/package.json b/packages/plugins/package.json index b741c0359b32b1..a926c2fd9a4e17 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", @@ -43,6 +44,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/preferences-persistence/package.json b/packages/preferences-persistence/package.json index e2623eac1a021c..05d2cf7031929d 100644 --- a/packages/preferences-persistence/package.json +++ b/packages/preferences-persistence/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", @@ -33,6 +34,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/preferences/package.json b/packages/preferences/package.json index ef231f359ba143..205f9b5d4966b1 100644 --- a/packages/preferences/package.json +++ b/packages/preferences/package.json @@ -27,6 +27,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7", @@ -47,6 +48,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/primitives/package.json b/packages/primitives/package.json index 04c3cc86dddec3..d24bc1acf25e2d 100644 --- a/packages/primitives/package.json +++ b/packages/primitives/package.json @@ -26,10 +26,11 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, + "types": "build-types", "sideEffects": [ "src/**/*.scss" ], - "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/element": "*", @@ -40,6 +41,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index 91020c0f81db24..8c98b0b3552198 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -34,6 +35,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/private-apis/package.json b/packages/private-apis/package.json index 9364a596c39af0..84436470521332 100644 --- a/packages/private-apis/package.json +++ b/packages/private-apis/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -33,6 +34,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/react-i18n/package.json b/packages/react-i18n/package.json index d98a4d7711f89c..31c4d98fa475e0 100644 --- a/packages/react-i18n/package.json +++ b/packages/react-i18n/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -35,6 +36,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index 489bc14632ded2..b3aa6fa79474a8 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -27,6 +27,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -40,6 +41,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/reusable-blocks/package.json b/packages/reusable-blocks/package.json index 82a812163a0ccc..e2649609016642 100644 --- a/packages/reusable-blocks/package.json +++ b/packages/reusable-blocks/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": [ "{src,build,build-module}/{index.js,store/index.js}" ], @@ -48,6 +49,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 4a5fe0f8e9d5ca..f038e097668984 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -25,11 +25,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, + "types": "build-types", "sideEffects": [ "src/**/*.scss", "{src,build,build-module}/{index.js,store/index.js}" ], - "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/a11y": "*", @@ -47,6 +48,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/router/package.json b/packages/router/package.json index 1822e79a317102..3d80481c9b6baa 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7", @@ -38,6 +39,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index 0bb27f17ad62d0..cdfe9679c5049d 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/api-fetch": "*", @@ -45,6 +46,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index f85efeb91e1803..c476788e3cfaf8 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -25,12 +25,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "memize": "^2.0.1" }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/style-engine/package.json b/packages/style-engine/package.json index 76ce2ccbb1445d..378f4dce91dad9 100644 --- a/packages/style-engine/package.json +++ b/packages/style-engine/package.json @@ -27,6 +27,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -35,6 +36,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/sync/package.json b/packages/sync/package.json index 95be09803a75f1..d7181479327d3a 100644 --- a/packages/sync/package.json +++ b/packages/sync/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -41,6 +42,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 0d966edeb9ee7d..370d50e5b4700a 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -25,12 +25,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "dependencies": { "@babel/runtime": "7.25.7" }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/undo-manager/package.json b/packages/undo-manager/package.json index f0464d7fd2f9e2..66f895f8da887d 100644 --- a/packages/undo-manager/package.json +++ b/packages/undo-manager/package.json @@ -26,6 +26,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -34,6 +35,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/url/package.json b/packages/url/package.json index 885efe28f8e8b4..2ecdb8eae18113 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "dependencies": { @@ -33,6 +34,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 05e098f65e1aeb..224da8a871addf 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -25,6 +25,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/compose": "*", @@ -36,6 +37,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/vips/package.json b/packages/vips/package.json index 810b8d2b5c9fc4..69912b2eaed71e 100644 --- a/packages/vips/package.json +++ b/packages/vips/package.json @@ -25,12 +25,12 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "wpScript": true, "types": "build-types", "dependencies": { "wasm-vips": "^0.0.10" }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/warning/package.json b/packages/warning/package.json index cae2aefa421ffe..d9c5a4dd83dc10 100644 --- a/packages/warning/package.json +++ b/packages/warning/package.json @@ -25,10 +25,10 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "types": "build-types", "sideEffects": false, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/widgets/package.json b/packages/widgets/package.json index b7fa362d615856..a755890c48ed92 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -23,6 +23,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "dependencies": { "@babel/runtime": "7.25.7", "@wordpress/api-fetch": "*", @@ -44,6 +45,5 @@ }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index a9f8304f79d8ff..117e2227a926c8 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -25,12 +25,12 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "wpScript": true, "sideEffects": false, "dependencies": { "@babel/runtime": "7.25.7" }, "publishConfig": { "access": "public" - }, - "wpScript": true + } } From e6ee784b3b4e348e4f47369a94c7c86f5469df68 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:23:19 +0100 Subject: [PATCH 013/605] Site Editor: fix template for page-on-front option (#66739) Co-authored-by: ellatrix Co-authored-by: draganescu Co-authored-by: ntsekouras Co-authored-by: mdawaffe --- .../use-init-edited-entity-from-url.js | 27 +++++++++++++- .../site-editor/template-hierarchy.spec.js | 35 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 test/e2e/specs/site-editor/template-hierarchy.spec.js diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index 3498bed4c99a56..2dbd143766e67c 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -96,7 +96,32 @@ function useResolveEditedEntityAndContext( { postId, postType } ) { postTypeToResolve === 'page' && homepageId === postIdToResolve ) { - return getDefaultTemplateId( { slug: 'front-page' } ); + // The /lookup endpoint cannot currently handle a lookup + // when a page is set as the front page, so specifically in + // that case, we want to check if there is a front page + // template, and instead of falling back to the home + // template, we want to fall back to the page template. + const templates = getEntityRecords( + 'postType', + TEMPLATE_POST_TYPE, + { + per_page: -1, + } + ); + if ( templates ) { + const id = templates?.find( + ( { slug } ) => slug === 'front-page' + )?.id; + if ( id ) { + return id; + } + + // If no front page template is found, continue with the + // logic below (fetching the page template). + } else { + // Still resolving `templates`. + return undefined; + } } const editedEntity = getEditedEntityRecord( diff --git a/test/e2e/specs/site-editor/template-hierarchy.spec.js b/test/e2e/specs/site-editor/template-hierarchy.spec.js new file mode 100644 index 00000000000000..5fcc77da9942ce --- /dev/null +++ b/test/e2e/specs/site-editor/template-hierarchy.spec.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Template hierarchy', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyfour' ); + } ); + + test.afterEach( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'twentytwentyone' ); + } ); + + test( 'shows correct template with page on front option', async ( { + admin, + page, + editor, + } ) => { + await admin.visitAdminPage( 'options-reading.php' ); + await page.click( 'input[name="show_on_front"][value="page"]' ); + await page.selectOption( 'select[name="page_on_front"]', '2' ); + await page.click( 'input[type="submit"]' ); + await admin.visitSiteEditor(); + + // Title block should contain "Sample Page" + await expect( + editor.canvas.locator( 'role=document[name="Block: Title"]' ) + ).toContainText( 'Sample Page' ); + + await admin.visitAdminPage( 'options-reading.php' ); + await page.click( 'input[name="show_on_front"][value="posts"]' ); + await page.click( 'input[type="submit"]' ); + } ); +} ); From bdc3ab224accbc42402a372ded80212c8b2ebd50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Tue, 5 Nov 2024 17:35:20 +0100 Subject: [PATCH 014/605] WP Scripts: Make watch mode more resilient for developer errors (#66752) * WP Scripts: Fix the watch mode so it continues working when errors pop up while developing * Improve the handling for parsing JSON files * Docs: Add changelog entry * Improve the warnings messages Co-authored-by: gziolo Co-authored-by: sirreal --- packages/scripts/CHANGELOG.md | 4 ++ packages/scripts/utils/config.js | 102 ++++++++++++++++--------------- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index ca5a44bca62b52..c09cfd1ac9aaaa 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fix + +- Make `start` script more resilient for developer errors ([#66752](https://github.com/WordPress/gutenberg/pull/66752)). + ## 30.4.0 (2024-10-30) ### Enhancements diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index dfb44730438c4a..3d99f3784859df 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -1,7 +1,6 @@ /** * External dependencies */ -const chalk = require( 'chalk' ); const { readFileSync } = require( 'fs' ); const { basename, dirname, extname, join, sep } = require( 'path' ); const { sync: glob } = require( 'fast-glob' ); @@ -21,7 +20,8 @@ const { getBlockJsonModuleFields, getBlockJsonScriptFields, } = require( './block-json' ); -const { log } = console; + +const { warn } = console; // See https://babeljs.io/docs/en/config-files#configuration-file-types. const hasBabelConfig = () => @@ -209,10 +209,8 @@ function getWebpackEntryPoints( buildType ) { // Continue only if the source directory exists. if ( ! hasProjectFile( getWordPressSrcDirectory() ) ) { - log( - chalk.yellow( - `Source directory "${ getWordPressSrcDirectory() }" was not found. Please confirm there is a "src" directory in the root or the value passed to --webpack-src-dir is correct.` - ) + warn( + `Source directory "${ getWordPressSrcDirectory() }" was not found. Please confirm there is a "src" directory in the root or the value passed to --webpack-src-dir is correct.` ); return {}; } @@ -240,12 +238,13 @@ function getWebpackEntryPoints( buildType ) { try { parsedBlockJson = JSON.parse( fileContents ); } catch ( error ) { - chalk.yellow( - `Skipping "${ blockMetadataFile.replace( + warn( + `Not scanning "${ blockMetadataFile.replace( fromProjectRoot( sep ), '' - ) }" due to malformed JSON.` + ) }" due to collect entry points due to malformed JSON.` ); + continue; } const fields = @@ -270,18 +269,16 @@ function getWebpackEntryPoints( buildType ) { // Takes the path without the file extension, and relative to the defined source directory. if ( ! filepath.startsWith( srcDirectory ) ) { - log( - chalk.yellow( - `Skipping "${ value.replace( - 'file:', - '' - ) }" listed in "${ blockMetadataFile.replace( - fromProjectRoot( sep ), - '' - ) }". File is located outside of the "${ getWordPressSrcDirectory() }" directory.` - ) + warn( + `Skipping "${ value.replace( + 'file:', + '' + ) }" listed in "${ blockMetadataFile.replace( + fromProjectRoot( sep ), + '' + ) }". File is located outside of the "${ getWordPressSrcDirectory() }" directory.` ); - return; + continue; } const entryName = filepath .replace( extname( filepath ), '' ) @@ -298,18 +295,16 @@ function getWebpackEntryPoints( buildType ) { ); if ( ! entryFilepath ) { - log( - chalk.yellow( - `Skipping "${ value.replace( - 'file:', - '' - ) }" listed in "${ blockMetadataFile.replace( - fromProjectRoot( sep ), - '' - ) }". File does not exist in the "${ getWordPressSrcDirectory() }" directory.` - ) + warn( + `Skipping "${ value.replace( + 'file:', + '' + ) }" listed in "${ blockMetadataFile.replace( + fromProjectRoot( sep ), + '' + ) }". File does not exist in the "${ getWordPressSrcDirectory() }" directory.` ); - return; + continue; } entryPoints[ entryName ] = entryFilepath; } @@ -334,10 +329,8 @@ function getWebpackEntryPoints( buildType ) { } ); if ( ! entryFile ) { - log( - chalk.yellow( - `No entry file discovered in the "${ getWordPressSrcDirectory() }" directory.` - ) + warn( + `No entry file discovered in the "${ getWordPressSrcDirectory() }" directory.` ); return {}; } @@ -370,13 +363,24 @@ function getPhpFilePaths( context, props ) { const srcDirectory = fromProjectRoot( context + sep ); return blockMetadataFiles.flatMap( ( blockMetadataFile ) => { - const blockJson = JSON.parse( readFileSync( blockMetadataFile ) ); - const paths = []; + let parsedBlockJson; + try { + parsedBlockJson = JSON.parse( readFileSync( blockMetadataFile ) ); + } catch ( error ) { + warn( + `Not scanning "${ blockMetadataFile.replace( + fromProjectRoot( sep ), + '' + ) }" due to detect render files due to malformed JSON.` + ); + return paths; + } + for ( const prop of props ) { if ( - typeof blockJson?.[ prop ] !== 'string' || - ! blockJson[ prop ]?.startsWith( 'file:' ) + typeof parsedBlockJson?.[ prop ] !== 'string' || + ! parsedBlockJson[ prop ]?.startsWith( 'file:' ) ) { continue; } @@ -384,21 +388,19 @@ function getPhpFilePaths( context, props ) { // Removes the `file:` prefix. const filepath = join( dirname( blockMetadataFile ), - blockJson[ prop ].replace( 'file:', '' ) + parsedBlockJson[ prop ].replace( 'file:', '' ) ); // Takes the path without the file extension, and relative to the defined source directory. if ( ! filepath.startsWith( srcDirectory ) ) { - log( - chalk.yellow( - `Skipping "${ blockJson[ prop ].replace( - 'file:', - '' - ) }" listed in "${ blockMetadataFile.replace( - fromProjectRoot( sep ), - '' - ) }". File is located outside of the "${ context }" directory.` - ) + warn( + `Skipping "${ parsedBlockJson[ prop ].replace( + 'file:', + '' + ) }" listed in "${ blockMetadataFile.replace( + fromProjectRoot( sep ), + '' + ) }". File is located outside of the "${ context }" directory.` ); continue; } From d50ff80f4f4e688838cf3f2972cf7213e3acb35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:39:03 +0100 Subject: [PATCH 015/605] Documentation: add section about the Fields API (#66761) --- packages/dataviews/README.md | 454 +++++++++++++++++++++++++++++++---- 1 file changed, 407 insertions(+), 47 deletions(-) diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index d61df206409152..fe35ae8f0aaa9a 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -67,7 +67,7 @@ By default, dataviews would use each record's `id` as an unique identifier. If t #### `fields`: `Object[]` -The fields describe the visible items for each record in the dataset. +The fields describe the visible items for each record in the dataset and how they behave (how to sort them, display them, etc.). See "Fields API" as a reference for every property. Example: @@ -95,7 +95,7 @@ const fields = [ }, { id: 'author', - label: __( 'Author' ), + label: 'Author', render: ( { item } ) => { return { item.author }; }, @@ -110,7 +110,7 @@ const fields = [ }, { id: 'status', - label: __( 'Status' ), + label: 'Status', getValue: ( { item } ) => STATUSES.find( ( { value } ) => value === item.status )?.label ?? item.status, @@ -123,49 +123,6 @@ const fields = [ ]; ``` -Each field is an object with the following properties: - -- `id`: identifier for the field. Unique. -- `type`: the type of the field. See "Field types" section. -- `label`: the field's name to be shown in the UI. -- `header`: defaults to the label. Allows providing a React element to render the field labels. -- `getValue`: function that returns the value of the field, defaults to `field[id]`. -- `render`: function that renders the field. Optional, `getValue` will be used if `render` is not defined. -- `Edit`: function that renders an edit control for the field. Alternatively, can provide a string declaring one of default controls provided by DataForm `text`, `integer`, `datetime`, `radio`, `select`. -- `sort`: a compare function that determines the order of the records. -- `isValid`: callback to decide if a field should be displayed. -- `isVisible`: callback to decide if a field should be visible. -- `enableSorting`: whether the data can be sorted by the given field. True by default. -- `enableHiding`: whether the field can be hidden. True by default. -- `enableGlobalSearch`: whether the field is searchable. False by default. -- elements: The list of options to pick from when using the field as a filter or when editing (DataForm component). Providing a list of to the field automatically creates a filter for it. It expects an array of objects with the following properties: - - `value`: The id of the value to filter to (for internal use) - - `label`: The text that will be displayed in the UI for the item. - - `description`: A longer description that describes the element, to also be displayed. Optional. -- `filterBy`: configuration for the filters enabled by the `elements` property. - - `operators`: the list of operators supported by the field. See "Filter operators" below. - - `isPrimary`: whether it is a primary filter. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. - -##### Field types - -Current supported types include: `text`, `integer`, `datetime`. - -If a field declares a `type` the `sort`, `isValid`, and `Edit` functions will be provided with default implementations. They will overriden if the field provides its own. - -##### Filter operators - - -| Operator | Selection | Description | Example | -| ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | -| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin | -| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin | -| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor | -| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor | -| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | -| `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | - -`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. - #### `view`: `Object` The view object configures how the dataset is visible to the user. @@ -422,7 +379,33 @@ This could be thought as as a single record coming from the `data` property of ` #### `fields`: `Object[]` -Same as `fields` property of `DataViews`. +The fields describe which parts of the the data are visible and how they behave (how to edit them, validating them, etc.). See "Fields API" as a reference for every property. + +Example: + +```js +const fields = [ + { + id: 'title', + type: 'text', + label: 'Title', + }, + { + id: 'date', + type: 'datetime', + label: 'Date', + }, + { + id: 'author', + type: 'text' + label: 'Author', + elements: [ + { value: 1, label: 'Admin' }, + { value: 2, label: 'User' }, + ], + }, +]; +``` #### `form`: `Object[]` @@ -497,6 +480,383 @@ Parameters: Returns a boolean indicating if the item is valid (true) or not (false). +## Fields API + +### `id` + +The unique identifier of the field. + +- Type: `string`. +- Required. + +Example: + +```js +{ id: 'field_id' } +``` + +### `type` + +Field type. One of `text`, `integer`, `datetime`. + +If a field declares a `type`, it gets default implementations for the `sort`, `isValid`, and `Edit` functions. They will overriden if the field provides its own. + +- Type: `string`. +- Optional. + +Example: + +```js +{ type: 'text' } +``` + +### `label` + +The field's name. This will be used across the UI. + +- Type: `string`. +- Optional. +- Defaults to the `id` value. + +Example: + +```js +{ label: 'Title' } +``` + +### `header` + +React component used by the layouts to display the field name — useful to add icons, etc. It's complementary to the `label` property. + +- Type: React component. +- Optional. +- Defaults to the `label` value. +- Props: none. +- Returns a React element that represents the field's name. + +Example: + +```js +{ + header: () => { /* Returns a react element. */ } +} +``` + +### `getValue` + +React component that returns the value of a field. This value is used in sorting the fields, or when filtering. + +- Type: React component. +- Optional. +- Defaults to `item[ id ]`. +- Props: + - `item` value to be processed. +- Returns a value that represents the field. + +Example: + +```js +{ + getValue: ( { item } ) => { /* The field's value. */ }; +} +``` + +### `render` + +React component that renders the field. This is used by the layouts. + +- Type: React component. +- Optional. +- Defaults to `getValue`. +- Props + - `item` value to be processed. +- Returns a React element that represents the field's value. + +Example: + +```js +{ + render: ( { item} ) => { /* React element to be displayed. */ } +} +``` + +### `Edit` + +React component that renders the control to edit the field. + +- Type: React component | `string`. If it's a string, it needs to be one of `text`, `integer`, `datetime`, `radio`, `select`. +- Required by DataForm. Optional if the field provided a `type`. +- Props: + - `data`: the item to be processed + - `field`: the field definition + - `onChange`: the callback with the updates + - `hideLabelFromVision`: boolean representing if the label should be hidden +- Returns a React element to edit the field's value. + +Example: + +```js +// A custom control defined by the field. +{ + Edit: ( { + data, + field, + onChange, + hideLabelFromVision + } ) => { + const value = field.getValue( { item: data } ); + + return ( + + ); + } +} + +// Use one of the core controls. +{ + Edit: 'radio' +} + +// Edit is optional when field's type is present. +// The field will use the default Edit function for text. +{ + type: 'text' +} + +// Edit can be provided even if field's type is present. +// The field will use its own custom control. +{ + type: 'text', + Edit: 'radio' +} +``` + +### `sort` + +Function to sort the records. + +- Type: `function`. +- Optional. +- Args + - `a`: the first item to compare + - `b`: the second item to compare + - `direction`: either `asc` (ascending) or `desc` (descending) +- Returns a number where: + - a negative value indicates that `a` should come before `b` + - a positive value indicates that `a` should come after `b` + - 0 indicates that `a` and `b` are considered equal + +Example: + +```js +// A custom sort function defined by the field. +{ + sort: ( a, b, direction ) => { + return direction === 'asc' + ? a.localeCompare( b ) + : b.localeCompare( a ); + } +} +``` + +```js +// If field type is provided, +// the field gets a default sort function. +{ + type: 'number' +} +``` + +```js +// Even if a field type is provided, +// fields can override the default sort function assigned for that type. +{ + type: 'number' + sort: ( a, b, direction ) => { /* Custom sort */ } +} +``` + +### `isValid` + +Function to validate a field's value. + +- Type: function. +- Optional. +- Args + - `item`: the data to validate + - `context`: an object containing the following props: + - `elements`: the elements defined by the field +- Returns a boolean, indicating if the field is valid or not. + +Example: + +```js +// Custom isValid function. +{ + isValid: ( item, context ) => { + return !! item; + } +} +``` + +```js +// If the field defines a type, +// it'll get a default isValid function for the type. +{ + type: 'number', +} +``` + +```js +// Even if the field provides a type, +// the field can override the default isValid function. +{ + type: 'number', + isValid: ( item, context ) => { /* Custom function. */ } +} +``` + +### `isVisible` + +Function that indicates if the field should be visible. + +- Type: `function`. +- Optional. +- Args + - `item`: the data to be processed +- Returns a `boolean` indicating if the field should be visible (`true`) or not (`false`). + +Example: + +```js +// Custom isVisible function. +{ + isVisible: ( item ) => { /* Custom implementation. */ } +} +``` + +### `enableSorting` + +Boolean indicating if the field is sortable. + +- Type: `boolean`. +- Optional. +- Defaults to `true`. + +Example: + +```js +{ enableSorting: true } +``` + +### `enableHiding` + +Boolean indicating if the field can be hidden. + +- Type: `boolean`. +- Optional. +- Defaults to `true`. + +Example: + +```js +{ enableHiding: true } +``` + +### `enableGlobalSearch` + +Boolean indicating if the field is searchable. + +- Type: `boolean`. +- Optional. +- Defaults to `false`. + +Example: + +```js +{ enableGlobalSearch: true } +``` + +### `elements` + +List of valid values for a field. If provided, it creates a DataViews' filter for the field. DataForm's edit control will use these values as well (see `Edit` field property). + +- Type: `array` of objects. +- Optional. +- Each object can have the following properties: + - `value`: required, the value to match against the field's value. + - `label`: required, the name to display to users. + - `description`: optional, a longer description of the item. + +Example: + +```js +{ + elements: [ + { value: '1', label: 'Product A' }, + { value: '2', label: 'Product B' }, + { value: '3', label: 'Product C' }, + { value: '4', label: 'Product D' }, + ] +} +``` + +### `filterBy` + +Configuration of the filters. + +- Type: `object`. +- Optional. +- Properties: + - `operators`: the list of operators supported by the field. See "operators" below. By default, a filter will support the `isAny` and `isNone` multi-selection operators. + - `isPrimary`: boolean, optional. Indicates if the filter is primary. A primary filter is always visible and is not listed in the "Add filter" component, except for the list layout where it behaves like a secondary filter. + +Operators: + +| Operator | Selection | Description | Example | +| ---------- | -------------- | ----------------------------------------------------------------------- | -------------------------------------------------- | +| `is` | Single item | `EQUAL TO`. The item's field is equal to a single value. | Author is Admin | +| `isNot` | Single item | `NOT EQUAL TO`. The item's field is not equal to a single value. | Author is not Admin | +| `isAny` | Multiple items | `OR`. The item's field is present in a list of values. | Author is any: Admin, Editor | +| `isNone` | Multiple items | `NOT OR`. The item's field is not present in a list of values. | Author is none: Admin, Editor | +| `isAll` | Multiple items | `AND`. The item's field has all of the values in the list. | Category is all: Book, Review, Science Fiction | +| `isNotAll` | Multiple items | `NOT AND`. The item's field doesn't have all of the values in the list. | Category is not all: Book, Review, Science Fiction | + +`is` and `isNot` are single-selection operators, while `isAny`, `isNone`, `isAll`, and `isNotALl` are multi-selection. By default, a filter with no operators declared will support the `isAny` and `isNone` multi-selection operators. A filter cannot mix single-selection & multi-selection operators; if a single-selection operator is present in the list of valid operators, the multi-selection ones will be discarded and the filter won't allow selecting more than one item. + +Example: + +```js +// Set a filter as primary. +{ + filterBy: { + isPrimary: true + } +} +``` + +```js +// Configure a filter as single-selection. +{ + filterBy: { + operators: [ `is`, `isNot` ] + } +} +``` + +```js +// Configure a filter as multi-selection with all the options. +{ + filterBy: { + operators: [ `isAny`, `isNone`, `isAll`, `isNotAll` ] + } +} +``` + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. From 058354964253523d67f48318cf3eace66dd3de92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:12:49 +0100 Subject: [PATCH 016/605] Documentation: improve readability of DataViews docs (#66766) --- packages/dataviews/README.md | 60 ++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index fe35ae8f0aaa9a..ff20386862929e 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -1,9 +1,9 @@ # The `@wordpress/dataviews` package -The DataViews package offers two components to work with a given dataset: +The DataViews package offers two React components and a few utilites to work with a list of data: -- `DataViews`: allows rendering a dataset using different types of layouts (table, grid, list) and interaction capabilities (search, filters, sorting, etc.). -- `DataForm`: allows editing the items from the same dataset. +- `DataViews`: to render the dataset using different types of layouts (table, grid, list) and interaction capabilities (search, filters, sorting, etc.). +- `DataForm`: to edit the items of the dataset. ## Installation @@ -15,15 +15,19 @@ npm install @wordpress/dataviews --save ## `DataViews` + + ### Usage -The component is data agnostic, it just requires the data to be an array of objects with an unique identifier — it can work with data coming from a static (e.g.: JSON file) or dynamic source (e.g.: HTTP Request). Consumers are responsible to query the data source appropiately: +The `DataViews` component receives data and some other configuration to render the dataset. It'll call the `onChangeView` callback every time the user has interacted with the dataset in some way (sorted, filtered, changed layout, etc.): ![DataViews flow](https://developer.wordpress.org/files/2024/09/368600071-20aa078f-7c3d-406d-8dd0-8b764addd22a.png "DataViews flow") +Example: + ```jsx const Example = () => { - // Declare data, fields, etc. + const onChangeView = () => { /* React to user changes. */ } return ( { }; ``` - ### Properties #### `data`: `Object[]` -The dataset to work with, represented as a one-dimensional array. +A one-dimensional array of objects. Example: @@ -63,11 +66,28 @@ const data = [ ]; ``` -By default, dataviews would use each record's `id` as an unique identifier. If the records don't have a `id` property that identify them uniquely, they consumer needs to provide a `getItemId` function that returns an unique identifier for the record. +The data can come from anywhere, from a static JSON file to a dynamic source like a HTTP Request. It's the consumer's responsiblity to query the data source appropiately and update the dataset based on the user's choices for sorting, filtering, etc. + +Each record should have an `id` that identifies them uniquely. If they don't, the consumer should provide the `getItemId` property to `DataViews`: a function that returns an unique identifier for the record. + +#### `getItemId`: `function` + +Function that receives an item and returns an unique identifier for it. + +It's optional. The field will get a default implementation by `DataViews` that returns the value of the `item[ id ]`. + +Example: + +```js +// Custom getItemId function. +{ + getItemId={ ( item ) => item.name ?? item.id } +} +``` #### `fields`: `Object[]` -The fields describe the visible items for each record in the dataset and how they behave (how to sort them, display them, etc.). See "Fields API" as a reference for every property. +The fields describe the visible items for each record in the dataset and how they behave (how to sort them, display them, etc.). See "Fields API" for a description of every property. Example: @@ -304,12 +324,6 @@ Whether the search input is enabled. `true` by default. What text to show in the search input. "Search" by default. -#### `getItemId`: `function` - -Function that receives an item and returns an unique identifier for it. - -By default, dataviews would use each record's `id` as an unique identifier. If the records don't have a `id` property that identify them uniquely, they consumer needs to provide a `getItemId` function that returns an unique identifier for the record. - #### `isLoading`: `boolean` Whether the data is loading. `false` by default. @@ -350,6 +364,8 @@ React component to be rendered next to the view config button. ## `DataForm` + + ### Usage ```jsx @@ -367,19 +383,17 @@ const Example = () => { } ``` - - ### Properties #### `data`: `Object` A single item to be edited. -This could be thought as as a single record coming from the `data` property of `DataViews` — though it doesn't need to be; it can be, for example, a mix of records if you support bulk editing. +It can be think of as a single record coming from the `data` property of `DataViews` — though it doesn't need to be. It can be totally separated or a mix of records if your app supports bulk editing. #### `fields`: `Object[]` -The fields describe which parts of the the data are visible and how they behave (how to edit them, validating them, etc.). See "Fields API" as a reference for every property. +The fields describe which parts of the data are visible and how they behave (how to edit them, validate them, etc.). See "Fields API" for a description of every property. Example: @@ -593,7 +607,7 @@ React component that renders the control to edit the field. - `hideLabelFromVision`: boolean representing if the label should be hidden - Returns a React element to edit the field's value. -Example: +Example: ```js // A custom control defined by the field. @@ -615,18 +629,24 @@ Example: ); } } +``` +```js // Use one of the core controls. { Edit: 'radio' } +``` +```js // Edit is optional when field's type is present. // The field will use the default Edit function for text. { type: 'text' } +``` +```js // Edit can be provided even if field's type is present. // The field will use its own custom control. { From c41b2194d01e4f34ef101bf244c62c5265b99345 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 5 Nov 2024 18:39:00 +0100 Subject: [PATCH 017/605] Zoom-out: Move default background to the iframe component (#66284) Co-authored-by: youknowriad Co-authored-by: ntsekouras Co-authored-by: ellatrix Co-authored-by: jasmussen Co-authored-by: jameskoster --- .../src/components/block-canvas/style.scss | 2 +- .../src/components/editor-canvas-container/style.scss | 1 - .../editor/src/components/visual-editor/style.scss | 11 +++++++++++ storybook/stories/playground/zoom-out/index.js | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-canvas/style.scss b/packages/block-editor/src/components/block-canvas/style.scss index 0b37b4dd1447e2..8f6064de0b615c 100644 --- a/packages/block-editor/src/components/block-canvas/style.scss +++ b/packages/block-editor/src/components/block-canvas/style.scss @@ -3,7 +3,7 @@ iframe[name="editor-canvas"] { width: 100%; height: 100%; display: block; - background-color: transparent; // Handles transitions between device previews @include editor-canvas-resize-animation; + background-color: $gray-300; } diff --git a/packages/edit-site/src/components/editor-canvas-container/style.scss b/packages/edit-site/src/components/editor-canvas-container/style.scss index 0bdbc2bbe32355..52ac29da0696f6 100644 --- a/packages/edit-site/src/components/editor-canvas-container/style.scss +++ b/packages/edit-site/src/components/editor-canvas-container/style.scss @@ -1,6 +1,5 @@ .edit-site-editor-canvas-container { height: 100%; - background-color: $gray-300; // Controls height of editor and editor canvas container (style book, global styles revisions previews etc.) iframe { diff --git a/packages/editor/src/components/visual-editor/style.scss b/packages/editor/src/components/visual-editor/style.scss index fae61eda6b8013..63df28f0f1ba5a 100644 --- a/packages/editor/src/components/visual-editor/style.scss +++ b/packages/editor/src/components/visual-editor/style.scss @@ -1,8 +1,19 @@ .editor-visual-editor { position: relative; display: flex; + + // This duplicates the iframe background but it's necessary in some situations + // when the iframe doesn't cover the whole canvas + // like the "focused entities". background-color: $gray-300; + // This overrides the iframe background since it's applied again here + // It also prevents some style glitches if `editor-visual-editor` + // like when hovering the preview in the site editor. + iframe[name="editor-canvas"] { + background-color: transparent; + } + // Centralize the editor horizontally (flex-direction is column). align-items: center; diff --git a/storybook/stories/playground/zoom-out/index.js b/storybook/stories/playground/zoom-out/index.js index 4f2efcf48523e8..8b72a831d710e8 100644 --- a/storybook/stories/playground/zoom-out/index.js +++ b/storybook/stories/playground/zoom-out/index.js @@ -50,7 +50,7 @@ export default function EditorZoomOut( { zoomLevel } ) {
event.stopPropagation() } - style={ { background: '#ddd', border: '1px solid gray' } } + style={ { border: '1px solid gray' } } > Date: Tue, 5 Nov 2024 18:44:39 +0100 Subject: [PATCH 018/605] BlockPatternsList: use the Async component (#66744) Co-authored-by: ellatrix Co-authored-by: mcsf --- package-lock.json | 1 + packages/block-editor/package.json | 1 + .../components/block-patterns-list/README.md | 8 --- .../components/block-patterns-list/index.js | 50 +++++++++---------- .../stories/index.story.js | 11 +--- .../src/components/block-preview/async.js} | 0 .../src/components/block-preview/index.js | 7 ++- .../components/block-toolbar/change-design.js | 6 --- .../block-patterns-explorer/pattern-list.js | 3 -- .../pattern-category-previews.js | 1 - .../inserter/hooks/use-patterns-paging.js | 7 +-- .../src/components/inserter/search-results.js | 6 --- packages/block-editor/tsconfig.json | 1 + .../src/query/edit/pattern-selection-modal.js | 3 -- .../src/template-part/edit/index.js | 3 -- .../src/template-part/edit/selection-modal.js | 3 -- .../src/components/page-patterns/fields.js | 5 +- .../src/components/page-templates/fields.js | 5 +- .../post-template/swap-template-button.js | 3 -- .../components/post-transform-panel/index.js | 3 -- .../components/start-page-options/index.js | 3 -- .../start-template-options/index.js | 3 -- 22 files changed, 38 insertions(+), 95 deletions(-) rename packages/{edit-site/src/components/async/index.js => block-editor/src/components/block-preview/async.js} (100%) diff --git a/package-lock.json b/package-lock.json index 9d5b12e749d123..e76ea379cd1a6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53169,6 +53169,7 @@ "@wordpress/keycodes": "*", "@wordpress/notices": "*", "@wordpress/preferences": "*", + "@wordpress/priority-queue": "*", "@wordpress/private-apis": "*", "@wordpress/rich-text": "*", "@wordpress/style-engine": "*", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 26b06474f54b18..b4672bc57690eb 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -60,6 +60,7 @@ "@wordpress/keycodes": "*", "@wordpress/notices": "*", "@wordpress/preferences": "*", + "@wordpress/priority-queue": "*", "@wordpress/private-apis": "*", "@wordpress/rich-text": "*", "@wordpress/style-engine": "*", diff --git a/packages/block-editor/src/components/block-patterns-list/README.md b/packages/block-editor/src/components/block-patterns-list/README.md index f63ea449059572..18e7ead5d1805a 100644 --- a/packages/block-editor/src/components/block-patterns-list/README.md +++ b/packages/block-editor/src/components/block-patterns-list/README.md @@ -18,7 +18,6 @@ import { BlockPatternsList } from '@wordpress/block-editor'; const MyBlockPatternsList = () => ( ); @@ -33,13 +32,6 @@ An array of block patterns that can be shown in the block patterns list. - Type: `Array` - Required: Yes -#### shownPatterns - -An array of shown block patterns objects. - -- Type: `Array` -- Required: Yes - #### onClickPattern The performed event after a click on a block pattern. In most cases, the pattern is inserted in the block editor. diff --git a/packages/block-editor/src/components/block-patterns-list/index.js b/packages/block-editor/src/components/block-patterns-list/index.js index 2609cc2db97a13..741a92ddf10dfa 100644 --- a/packages/block-editor/src/components/block-patterns-list/index.js +++ b/packages/block-editor/src/components/block-patterns-list/index.js @@ -134,10 +134,14 @@ function BlockPattern( { } } onMouseLeave={ () => onHover?.( null ) } > - + } + > + + { showTitle && ( - shownPatterns.includes( pattern ) - )?.name; + const firstCompositeItemId = blockPatterns[ 0 ]?.name; setActiveCompositeId( firstCompositeItemId ); - }, [ shownPatterns, blockPatterns ] ); + }, [ blockPatterns ] ); return ( - { blockPatterns.map( ( pattern ) => { - const isShown = shownPatterns.includes( pattern ); - return isShown ? ( - - ) : ( - - ); - } ) } + { blockPatterns.map( ( pattern ) => ( + + ) ) } { pagingProps && } ); diff --git a/packages/block-editor/src/components/block-patterns-list/stories/index.story.js b/packages/block-editor/src/components/block-patterns-list/stories/index.story.js index 9eb393ea13e762..0ebb4520d98fd4 100644 --- a/packages/block-editor/src/components/block-patterns-list/stories/index.story.js +++ b/packages/block-editor/src/components/block-patterns-list/stories/index.story.js @@ -3,11 +3,6 @@ */ import blockLibraryStyles from '!!raw-loader!../../../../../block-library/build-style/style.css'; -/** - * WordPress dependencies - */ -import { useAsyncList } from '@wordpress/compose'; - /** * Internal dependencies */ @@ -26,13 +21,9 @@ export default { export const Default = { render: function Template( props ) { - const shownPatterns = useAsyncList( props.blockPatterns ); return ( - + ); }, diff --git a/packages/edit-site/src/components/async/index.js b/packages/block-editor/src/components/block-preview/async.js similarity index 100% rename from packages/edit-site/src/components/async/index.js rename to packages/block-editor/src/components/block-preview/async.js diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 9eef0f1dc2abd5..62b137ff37194b 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -19,6 +19,7 @@ import AutoHeightBlockPreview from './auto'; import EditorStyles from '../editor-styles'; import { store as blockEditorStore } from '../../store'; import { BlockListItems } from '../block-list'; +import { Async } from './async'; const EMPTY_ADDITIONAL_STYLES = []; @@ -86,6 +87,10 @@ export function BlockPreview( { ); } +const MemoizedBlockPreview = memo( BlockPreview ); + +MemoizedBlockPreview.Async = Async; + /** * BlockPreview renders a preview of a block or array of blocks. * @@ -97,7 +102,7 @@ export function BlockPreview( { * * @return {Component} The component to be rendered. */ -export default memo( BlockPreview ); +export default MemoizedBlockPreview; /** * This hook is used to lightly mark an element as a block preview wrapper diff --git a/packages/block-editor/src/components/block-toolbar/change-design.js b/packages/block-editor/src/components/block-toolbar/change-design.js index ecfeff6cb1ed3e..9da1affe4273cc 100644 --- a/packages/block-editor/src/components/block-toolbar/change-design.js +++ b/packages/block-editor/src/components/block-toolbar/change-design.js @@ -10,7 +10,6 @@ import { import { __ } from '@wordpress/i18n'; import { cloneBlock } from '@wordpress/blocks'; import { useMemo } from '@wordpress/element'; -import { useAsyncList } from '@wordpress/compose'; import { useSelect, useDispatch } from '@wordpress/data'; /** @@ -81,10 +80,6 @@ export default function ChangeDesign( { clientId } ) { .slice( 0, MAX_PATTERNS_TO_SHOW ); }, [ categories, currentPatternName, patterns ] ); - const currentShownPatterns = useAsyncList( - sameCategoryPatternsWithSingleWrapper - ); - if ( sameCategoryPatternsWithSingleWrapper.length < 2 ) { return null; } @@ -121,7 +116,6 @@ export default function ChangeDesign( { clientId } ) { paddingSize="none" > { const scrollContainer = getScrollContainer( @@ -68,7 +64,6 @@ export default function usePatternsPaging( return { totalItems, categoryPatterns, - categoryPatternsAsyncList, numPages, changePage, currentPage, diff --git a/packages/block-editor/src/components/inserter/search-results.js b/packages/block-editor/src/components/inserter/search-results.js index 9c001823745e6c..5a5725a3bb08cd 100644 --- a/packages/block-editor/src/components/inserter/search-results.js +++ b/packages/block-editor/src/components/inserter/search-results.js @@ -159,11 +159,6 @@ function InserterSearchResults( { const currentShownBlockTypes = useAsyncList( filteredBlockTypes, { step: INITIAL_INSERTER_RESULTS, } ); - const currentShownPatterns = useAsyncList( - currentShownBlockTypes.length === filteredBlockTypes.length - ? filteredBlockPatterns - : EMPTY_ARRAY - ); const hasItems = filteredBlockTypes.length > 0 || filteredBlockPatterns.length > 0; @@ -190,7 +185,6 @@ function InserterSearchResults( { >
{ return searchPatterns( blockPatterns, searchValue ); }, [ blockPatterns, searchValue ] ); - const shownBlockPatterns = useAsyncList( filteredBlockPatterns ); return ( diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index ae941c977e14fd..a318fd285cdc33 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -21,7 +21,6 @@ import { MenuItem, ToolbarButton, } from '@wordpress/components'; -import { useAsyncList } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; import { useState } from '@wordpress/element'; @@ -85,7 +84,6 @@ function TemplatesList( { area, clientId, isEntityAvailable, onSelect } ) { isEntityAvailable && !! blockPatterns.length && ( area === 'header' || area === 'footer' ); - const shownTemplates = useAsyncList( blockPatterns ); if ( ! canReplace ) { return null; @@ -96,7 +94,6 @@ function TemplatesList( { area, clientId, isEntityAvailable, onSelect } ) { diff --git a/packages/block-library/src/template-part/edit/selection-modal.js b/packages/block-library/src/template-part/edit/selection-modal.js index 5fa5d9786d8c96..71093306b93b2f 100644 --- a/packages/block-library/src/template-part/edit/selection-modal.js +++ b/packages/block-library/src/template-part/edit/selection-modal.js @@ -5,7 +5,6 @@ import { useMemo, useState } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { useDispatch } from '@wordpress/data'; -import { useAsyncList } from '@wordpress/compose'; import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; import { SearchControl, @@ -43,7 +42,6 @@ export default function TemplatePartSelectionModal( { return searchPatterns( partsAsPatterns, searchValue ); }, [ templateParts, searchValue ] ); - const shownTemplateParts = useAsyncList( filteredTemplateParts ); const blockPatterns = useAlternativeBlockPatterns( area, clientId ); const filteredBlockPatterns = useMemo( () => { return searchPatterns( blockPatterns, searchValue ); @@ -89,7 +87,6 @@ export default function TemplatePartSelectionModal( {

{ __( 'Existing template parts' ) }

{ onTemplatePartSelect( pattern.templatePart ); } } diff --git a/packages/edit-site/src/components/page-patterns/fields.js b/packages/edit-site/src/components/page-patterns/fields.js index 74433b3de72f0e..0ad47e90c20402 100644 --- a/packages/edit-site/src/components/page-patterns/fields.js +++ b/packages/edit-site/src/components/page-patterns/fields.js @@ -25,7 +25,6 @@ import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies */ -import { Async } from '../async'; import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, @@ -88,12 +87,12 @@ function PreviewField( { item } ) { { isEmpty && isTemplatePart && __( 'Empty template part' ) } { isEmpty && ! isTemplatePart && __( 'Empty pattern' ) } { ! isEmpty && ( - + - + ) } { !! description && ( diff --git a/packages/edit-site/src/components/page-templates/fields.js b/packages/edit-site/src/components/page-templates/fields.js index d26f1906a10664..69e0596bf49d47 100644 --- a/packages/edit-site/src/components/page-templates/fields.js +++ b/packages/edit-site/src/components/page-templates/fields.js @@ -20,7 +20,6 @@ import { EditorProvider } from '@wordpress/editor'; /** * Internal dependencies */ -import { Async } from '../async'; import { default as Link, useLink } from '../routes/link'; import { useAddedBy } from './hooks'; @@ -63,9 +62,9 @@ function PreviewField( { item } ) { > { isEmpty && __( 'Empty template' ) } { ! isEmpty && ( - + - + ) }
diff --git a/packages/editor/src/components/post-template/swap-template-button.js b/packages/editor/src/components/post-template/swap-template-button.js index 5eed287f7f1709..bdda349398406b 100644 --- a/packages/editor/src/components/post-template/swap-template-button.js +++ b/packages/editor/src/components/post-template/swap-template-button.js @@ -9,7 +9,6 @@ import { __ } from '@wordpress/i18n'; import { useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { parse } from '@wordpress/blocks'; -import { useAsyncList } from '@wordpress/compose'; /** * Internal dependencies @@ -71,12 +70,10 @@ function TemplatesList( { postType, onSelect } ) { } ) ), [ availableTemplates ] ); - const shownTemplates = useAsyncList( templatesAsPatterns ); return ( ); diff --git a/packages/editor/src/components/post-transform-panel/index.js b/packages/editor/src/components/post-transform-panel/index.js index 08139c2c3c6645..88ca6dc3965a11 100644 --- a/packages/editor/src/components/post-transform-panel/index.js +++ b/packages/editor/src/components/post-transform-panel/index.js @@ -5,7 +5,6 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { PanelBody } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useAsyncList } from '@wordpress/compose'; import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; import { serialize } from '@wordpress/blocks'; @@ -20,7 +19,6 @@ import { } from '../../store/constants'; function TemplatesList( { availableTemplates, onSelect } ) { - const shownTemplates = useAsyncList( availableTemplates ); if ( ! availableTemplates || availableTemplates?.length === 0 ) { return null; } @@ -29,7 +27,6 @@ function TemplatesList( { availableTemplates, onSelect } ) { diff --git a/packages/editor/src/components/start-page-options/index.js b/packages/editor/src/components/start-page-options/index.js index 07fee67fbed19b..783a4a224fa378 100644 --- a/packages/editor/src/components/start-page-options/index.js +++ b/packages/editor/src/components/start-page-options/index.js @@ -9,7 +9,6 @@ import { __experimentalBlockPatternsList as BlockPatternsList, } from '@wordpress/block-editor'; import { useSelect, useDispatch } from '@wordpress/data'; -import { useAsyncList } from '@wordpress/compose'; import { store as coreStore } from '@wordpress/core-data'; import { __unstableSerializeAndClean } from '@wordpress/blocks'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -66,7 +65,6 @@ export function useStartPatterns() { } function PatternSelection( { blockPatterns, onChoosePattern } ) { - const shownBlockPatterns = useAsyncList( blockPatterns ); const { editEntityRecord } = useDispatch( coreStore ); const { postType, postId } = useSelect( ( select ) => { const { getCurrentPostType, getCurrentPostId } = select( editorStore ); @@ -79,7 +77,6 @@ function PatternSelection( { blockPatterns, onChoosePattern } ) { return ( { editEntityRecord( 'postType', postType, postId, { blocks, diff --git a/packages/editor/src/components/start-template-options/index.js b/packages/editor/src/components/start-template-options/index.js index 3651c5c029a2c7..8d3910341aff43 100644 --- a/packages/editor/src/components/start-template-options/index.js +++ b/packages/editor/src/components/start-template-options/index.js @@ -6,7 +6,6 @@ import { __ } from '@wordpress/i18n'; import { useState, useMemo, useEffect } from '@wordpress/element'; import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; import { useSelect } from '@wordpress/data'; -import { useAsyncList } from '@wordpress/compose'; import { parse } from '@wordpress/blocks'; import { store as coreStore, useEntityBlockEditor } from '@wordpress/core-data'; @@ -111,11 +110,9 @@ function useStartPatterns( fallbackContent ) { function PatternSelection( { fallbackContent, onChoosePattern, postType } ) { const [ , , onChange ] = useEntityBlockEditor( 'postType', postType ); const blockPatterns = useStartPatterns( fallbackContent ); - const shownBlockPatterns = useAsyncList( blockPatterns ); return ( { onChange( blocks, { selection: undefined } ); onChoosePattern(); From 177c6a2fc12d818dc864cc95ea1875f4dbc1ff62 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 5 Nov 2024 21:45:28 +0400 Subject: [PATCH 019/605] Block Editor: Fix ESLint warning for 'useBlockTypesState' hook (#66757) Co-authored-by: Mamaduka Co-authored-by: up1512001 --- .../src/components/inserter/hooks/use-block-types-state.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js index 6f11060c75c494..2ff8f1a1dc23c0 100644 --- a/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js +++ b/packages/block-editor/src/components/inserter/hooks/use-block-types-state.js @@ -94,7 +94,12 @@ const useBlockTypesState = ( rootClientId, onInsert, isQuick ) => { destinationClientId ); }, - [ onInsert, getClosestAllowedInsertionPoint, rootClientId ] + [ + getClosestAllowedInsertionPoint, + rootClientId, + onInsert, + createErrorNotice, + ] ); return [ items, categories, collections, onSelectItem ]; From ae29450a148520524057ca28841809c4e9054f55 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:57:14 +0100 Subject: [PATCH 020/605] Perf metrics: update select and other metrics to use non-empty paragraphs (#66762) Co-authored-by: ellatrix Co-authored-by: Mamaduka Co-authored-by: youknowriad --- test/performance/fixtures/perf-utils.ts | 4 ++-- test/performance/specs/post-editor.spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/performance/fixtures/perf-utils.ts b/test/performance/fixtures/perf-utils.ts index 83213a59520dd5..592e8194852e3b 100644 --- a/test/performance/fixtures/perf-utils.ts +++ b/test/performance/fixtures/perf-utils.ts @@ -150,7 +150,7 @@ export class PerfUtils { } /** - * Generates and loads a 1000 empty paragraphs into the editor canvas. + * Generates and loads a 1000 paragraphs into the editor canvas. */ async load1000Paragraphs() { await this.page.waitForFunction( @@ -161,7 +161,7 @@ export class PerfUtils { const { createBlock } = window.wp.blocks; const { dispatch } = window.wp.data; const blocks = Array.from( { length: 1000 } ).map( () => - createBlock( 'core/paragraph' ) + createBlock( 'core/paragraph', { content: 'paragraph' } ) ); dispatch( 'core/block-editor' ).resetBlocks( blocks ); } ); diff --git a/test/performance/specs/post-editor.spec.js b/test/performance/specs/post-editor.spec.js index becbf375eff2ac..85d334749f6f51 100644 --- a/test/performance/specs/post-editor.spec.js +++ b/test/performance/specs/post-editor.spec.js @@ -270,7 +270,7 @@ test.describe( 'Post Editor Performance', () => { const canvas = await perfUtils.getCanvas(); const paragraphs = canvas.getByRole( 'document', { - name: /Empty block/i, + name: /Block: Paragraph/i, } ); const samples = 10; From 7f49b39e1e3e2eaf274b6fb03ae22508cc583d31 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:38:09 +0100 Subject: [PATCH 021/605] Patterns: receive intermediate responses while unbound request is resolving (#66713) Co-authored-by: ellatrix Co-authored-by: youknowriad Co-authored-by: mcsf Co-authored-by: sathyapulse --- packages/core-data/src/private-apis.js | 2 + packages/core-data/src/resolvers.js | 57 ++++++++++++++++--- packages/core-data/src/utils/index.js | 1 + .../src/utils/receive-intermediate-results.js | 3 + .../provider/use-block-editor-settings.js | 14 ++--- 5 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 packages/core-data/src/utils/receive-intermediate-results.js diff --git a/packages/core-data/src/private-apis.js b/packages/core-data/src/private-apis.js index 443db97957285d..e9fcf36f7e6090 100644 --- a/packages/core-data/src/private-apis.js +++ b/packages/core-data/src/private-apis.js @@ -2,9 +2,11 @@ * Internal dependencies */ import { useEntityRecordsWithPermissions } from './hooks/use-entity-records'; +import { RECEIVE_INTERMEDIATE_RESULTS } from './utils'; import { lock } from './lock-unlock'; export const privateApis = {}; lock( privateApis, { useEntityRecordsWithPermissions, + RECEIVE_INTERMEDIATE_RESULTS, } ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index ae0c7f456e533d..a35403c0493460 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -21,6 +21,7 @@ import { getUserPermissionCacheKey, getUserPermissionsFromAllowHeader, ALLOWED_RESOURCE_ACTIONS, + RECEIVE_INTERMEDIATE_RESULTS, } from './utils'; import { getSyncProvider } from './sync'; import { fetchBlockPatterns } from './fetch'; @@ -245,6 +246,14 @@ export const getEntityRecords = { exclusive: false } ); + const key = entityConfig.key || DEFAULT_ENTITY_KEY; + + function getResolutionsArgs( records ) { + return records + .filter( ( record ) => record?.[ key ] ) + .map( ( record ) => [ kind, name, record[ key ] ] ); + } + try { if ( query._fields ) { // If requesting specific fields, items and query association to said @@ -267,7 +276,8 @@ export const getEntityRecords = ...query, } ); - let records, meta; + let records = [], + meta; if ( entityConfig.supportsPagination && query.per_page !== -1 ) { const response = await apiFetch( { path, parse: false } ); records = Object.values( await response.json() ); @@ -279,6 +289,44 @@ export const getEntityRecords = response.headers.get( 'X-WP-TotalPages' ) ), }; + } else if ( + query.per_page === -1 && + query[ RECEIVE_INTERMEDIATE_RESULTS ] === true + ) { + let page = 1; + let totalPages; + + do { + const response = await apiFetch( { + path: addQueryArgs( path, { page, per_page: 100 } ), + parse: false, + } ); + const pageRecords = Object.values( await response.json() ); + + totalPages = parseInt( + response.headers.get( 'X-WP-TotalPages' ) + ); + + records.push( ...pageRecords ); + registry.batch( () => { + dispatch.receiveEntityRecords( + kind, + name, + records, + query + ); + dispatch.finishResolutions( + 'getEntityRecord', + getResolutionsArgs( pageRecords ) + ); + } ); + page++; + } while ( page <= totalPages ); + + meta = { + totalItems: records.length, + totalPages: 1, + }; } else { records = Object.values( await apiFetch( { path } ) ); meta = { @@ -318,11 +366,6 @@ export const getEntityRecords = // See https://github.com/WordPress/gutenberg/pull/26575 // See https://github.com/WordPress/gutenberg/pull/64504 if ( ! query?._fields && ! query.context ) { - const key = entityConfig.key || DEFAULT_ENTITY_KEY; - const resolutionsArgs = records - .filter( ( record ) => record?.[ key ] ) - .map( ( record ) => [ kind, name, record[ key ] ] ); - const targetHints = records .filter( ( record ) => record?.[ key ] ) .map( ( record ) => ( { @@ -356,7 +399,7 @@ export const getEntityRecords = ); dispatch.finishResolutions( 'getEntityRecord', - resolutionsArgs + getResolutionsArgs( records ) ); dispatch.finishResolutions( 'canUser', diff --git a/packages/core-data/src/utils/index.js b/packages/core-data/src/utils/index.js index 189635647779e5..db10359bda07dd 100644 --- a/packages/core-data/src/utils/index.js +++ b/packages/core-data/src/utils/index.js @@ -14,3 +14,4 @@ export { getUserPermissionsFromAllowHeader, ALLOWED_RESOURCE_ACTIONS, } from './user-permissions'; +export { RECEIVE_INTERMEDIATE_RESULTS } from './receive-intermediate-results'; diff --git a/packages/core-data/src/utils/receive-intermediate-results.js b/packages/core-data/src/utils/receive-intermediate-results.js new file mode 100644 index 00000000000000..53d2295b28b390 --- /dev/null +++ b/packages/core-data/src/utils/receive-intermediate-results.js @@ -0,0 +1,3 @@ +export const RECEIVE_INTERMEDIATE_RESULTS = Symbol( + 'RECEIVE_INTERMEDIATE_RESULTS' +); diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 170ec96c107288..e90be6487a0ea9 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -7,6 +7,7 @@ import { store as coreStore, __experimentalFetchLinkSuggestions as fetchLinkSuggestions, __experimentalFetchUrlData as fetchUrlData, + privateApis as coreDataPrivateApis, } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -29,17 +30,12 @@ import { useGlobalStylesContext } from '../global-styles-provider'; const EMPTY_OBJECT = {}; function __experimentalReusableBlocksSelect( select ) { - const { getEntityRecords, hasFinishedResolution } = select( coreStore ); - const reusableBlocks = getEntityRecords( 'postType', 'wp_block', { + const { RECEIVE_INTERMEDIATE_RESULTS } = unlock( coreDataPrivateApis ); + const { getEntityRecords } = select( coreStore ); + return getEntityRecords( 'postType', 'wp_block', { per_page: -1, + [ RECEIVE_INTERMEDIATE_RESULTS ]: true, } ); - return hasFinishedResolution( 'getEntityRecords', [ - 'postType', - 'wp_block', - { per_page: -1 }, - ] ) - ? reusableBlocks - : undefined; } const BLOCK_EDITOR_SETTINGS = [ From 9bd184da7b547d1b46b2e118bde6f0529c71a2d8 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 6 Nov 2024 09:00:28 +0400 Subject: [PATCH 022/605] Edit Site: Remove leftover 'priority-queue' dependency (#66773) Co-authored-by: Mamaduka Co-authored-by: up1512001 --- package-lock.json | 1 - packages/edit-site/package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e76ea379cd1a6e..59fb1310a15e72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54310,7 +54310,6 @@ "@wordpress/plugins": "*", "@wordpress/preferences": "*", "@wordpress/primitives": "*", - "@wordpress/priority-queue": "*", "@wordpress/private-apis": "*", "@wordpress/reusable-blocks": "*", "@wordpress/router": "*", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 67932bcb06321e..41bf39c1e08332 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -61,7 +61,6 @@ "@wordpress/plugins": "*", "@wordpress/preferences": "*", "@wordpress/primitives": "*", - "@wordpress/priority-queue": "*", "@wordpress/private-apis": "*", "@wordpress/reusable-blocks": "*", "@wordpress/router": "*", From 471e4bdaf6f3b6e5b5ca37f8a00f12424870458c Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 6 Nov 2024 00:17:16 -0500 Subject: [PATCH 023/605] ci: Address stalling Android E2E test tasks (#66771) * ci: Increase AVD cache key specificity Avoid erroneous cache hits that may result in the CI task hanging indefinitely while terminating the emulator. * Revert "ci: Increase AVD cache key specificity" This reverts commit 2e892a66888295cf3edc1b33c4f4df7792fdbb0f. * ci: Update Android test runner to macOS 13 Attempt to resolve indefinitely hanging Android E2E test tasks. * Revert "ci: Update Android test runner to macOS 13" This reverts commit a4c844daf04dfcd517b293518d65d92063230726. * ci: Disable AVD cache AVD cache disabled as it caused emulator termination to hang indefinitely. Co-authored-by: dcalhoun Co-authored-by: Mamaduka --- .github/workflows/rnmobile-android-runner.yml | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/workflows/rnmobile-android-runner.yml b/.github/workflows/rnmobile-android-runner.yml index a57b857aac6234..65bb55e3f63201 100644 --- a/.github/workflows/rnmobile-android-runner.yml +++ b/.github/workflows/rnmobile-android-runner.yml @@ -49,26 +49,28 @@ jobs: - name: Gradle cache uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0 - - name: AVD cache - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # v2.33.0 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - arch: x86_64 - profile: Nexus 6 - script: echo "Generated AVD snapshot for caching." + # AVD cache disabled as it caused emulator termination to hang indefinitely. + # https://github.com/ReactiveCircus/android-emulator-runner/issues/385 + # - name: AVD cache + # uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + # id: avd-cache + # with: + # path: | + # ~/.android/avd/* + # ~/.android/adb* + # key: avd-${{ matrix.api-level }} + # + # - name: Create AVD and generate snapshot for caching + # if: steps.avd-cache.outputs.cache-hit != 'true' + # uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # v2.33.0 + # with: + # api-level: ${{ matrix.api-level }} + # force-avd-creation: false + # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + # disable-animations: false + # arch: x86_64 + # profile: Nexus 6 + # script: echo "Generated AVD snapshot for caching." - name: Run tests uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # v2.33.0 From 7ff76796b8fd7027a3f53933e1f9e5f2917ea2c8 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 6 Nov 2024 09:44:02 +0400 Subject: [PATCH 024/605] Cover: Fix media library image selection (#66782) * Cover: Fix media library image selection * Fix sizes for uploaded images Co-authored-by: Mamaduka Co-authored-by: aaronrobertshaw Co-authored-by: richtabor --- .../block-library/src/cover/edit/index.js | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/cover/edit/index.js b/packages/block-library/src/cover/edit/index.js index 0ad87601bfba8b..1202f28cee50cd 100644 --- a/packages/block-library/src/cover/edit/index.js +++ b/packages/block-library/src/cover/edit/index.js @@ -206,12 +206,24 @@ function CoverEdit( { // Try to use the previous selected image size if it's available // otherwise try the default image size or fallback to full size. - if ( sizeSlug && newMedia?.sizes?.[ sizeSlug ] ) { + if ( + sizeSlug && + ( newMedia?.sizes?.[ sizeSlug ] || + newMedia?.media_details?.sizes?.[ sizeSlug ] ) + ) { mediaAttributes.sizeSlug = sizeSlug; - mediaAttributes.url = newMedia?.sizes?.[ sizeSlug ]?.url; - } else if ( newMedia?.sizes?.[ imageDefaultSize ] ) { + mediaAttributes.url = + newMedia?.sizes?.[ sizeSlug ]?.url || + newMedia?.media_details?.sizes?.[ sizeSlug ]?.source_url; + } else if ( + newMedia?.sizes?.[ imageDefaultSize ] || + newMedia?.media_details?.sizes?.[ imageDefaultSize ] + ) { mediaAttributes.sizeSlug = imageDefaultSize; - mediaAttributes.url = newMedia?.sizes?.[ sizeSlug ]?.url; + mediaAttributes.url = + newMedia?.sizes?.[ imageDefaultSize ]?.url || + newMedia?.media_details?.sizes?.[ imageDefaultSize ] + ?.source_url; } else { mediaAttributes.sizeSlug = DEFAULT_MEDIA_SIZE_SLUG; } From 5c6e6f33a7288b14b6406a5494342c7711c6cd16 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Tue, 5 Nov 2024 23:21:12 -0800 Subject: [PATCH 025/605] Fix TS types for editor package (#66754) Co-authored-by: manzoorwanijk Co-authored-by: youknowriad --- docs/reference-guides/data/data-core-editor.md | 2 +- packages/editor/README.md | 8 -------- packages/editor/package.json | 1 + packages/editor/src/store/constants.ts | 2 -- packages/editor/src/store/index.js | 4 ---- packages/editor/src/store/selectors.js | 4 ++-- 6 files changed, 4 insertions(+), 17 deletions(-) diff --git a/docs/reference-guides/data/data-core-editor.md b/docs/reference-guides/data/data-core-editor.md index 713a247d88be29..9567d8e4b954fa 100644 --- a/docs/reference-guides/data/data-core-editor.md +++ b/docs/reference-guides/data/data-core-editor.md @@ -786,7 +786,7 @@ Return true if the current post has already been published. _Parameters_ - _state_ `Object`: Global application state. -- _currentPost_ `Object?`: Explicit current post for bypassing registry selector. +- _currentPost_ `[Object]`: Explicit current post for bypassing registry selector. _Returns_ diff --git a/packages/editor/README.md b/packages/editor/README.md index bcd1727e046d07..e0b53362089f1b 100644 --- a/packages/editor/README.md +++ b/packages/editor/README.md @@ -1631,10 +1631,6 @@ _Related_ - -_Type_ - -- `Object` - ### storeConfig Post editor data store configuration. @@ -1643,10 +1639,6 @@ _Related_ - -_Type_ - -- `Object` - ### TableOfContents Renders a table of contents component. diff --git a/packages/editor/package.json b/packages/editor/package.json index e7b46685719dd4..98e0c6d2255b74 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -26,6 +26,7 @@ "module": "build-module/index.js", "react-native": "src/index", "wpScript": true, + "types": "build-types", "sideEffects": [ "build-style/**", "src/**/*.scss", diff --git a/packages/editor/src/store/constants.ts b/packages/editor/src/store/constants.ts index 78708f00cc9eb7..73d6a104370c37 100644 --- a/packages/editor/src/store/constants.ts +++ b/packages/editor/src/store/constants.ts @@ -8,8 +8,6 @@ export const EDIT_MERGE_PROPERTIES = new Set( [ 'meta' ] ); /** * Constant for the store module (or reducer) key. - * - * @type {string} */ export const STORE_NAME = 'core/editor'; diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index 392f5f42cbd55b..a3241a2fc7e184 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -18,8 +18,6 @@ import { unlock } from '../lock-unlock'; * Post editor data store configuration. * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#registerStore - * - * @type {Object} */ export const storeConfig = { reducer, @@ -31,8 +29,6 @@ export const storeConfig = { * Store definition for the editor namespace. * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore - * - * @type {Object} */ export const store = createReduxStore( STORE_NAME, { ...storeConfig, diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 2396cb67c73e69..db452be5c17abb 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -442,8 +442,8 @@ export function isCurrentPostPending( state ) { /** * Return true if the current post has already been published. * - * @param {Object} state Global application state. - * @param {Object?} currentPost Explicit current post for bypassing registry selector. + * @param {Object} state Global application state. + * @param {Object} [currentPost] Explicit current post for bypassing registry selector. * * @return {boolean} Whether the post has been published. */ From 452d3b07a420e5b66d5c182a69f99c102a0af23e Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 6 Nov 2024 19:09:41 +1100 Subject: [PATCH 026/605] Fix Paragraph appender layout shift (building on 66061) (#66779) * Style to prevent Layout shift when placeholder is clicked * Refactor style placement to target block-appender__content specifically * Remove no longer needed rules --------- Co-authored-by: Vrishabhsk Co-authored-by: andrewserong Co-authored-by: ramonjd Co-authored-by: aaronrobertshaw Co-authored-by: jasmussen --- .../default-block-appender/content.scss | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/default-block-appender/content.scss b/packages/block-editor/src/components/default-block-appender/content.scss index e2009daa7358c4..71ede90d25c0ca 100644 --- a/packages/block-editor/src/components/default-block-appender/content.scss +++ b/packages/block-editor/src/components/default-block-appender/content.scss @@ -24,20 +24,11 @@ .block-editor-default-block-appender__content { // Set the opacity of the initial block appender to the same as placeholder text in an empty Paragraph block. opacity: 0.62; - } - - // In "constrained" layout containers, the first and last paragraphs have their margins zeroed out. - // In the case of this appender, it needs to apply those same rules to avoid layout shifts. - // Such shifts happen when the bottom margin of the Title block has been set to less than the default 1em margin of paragraphs. - :where(body .is-layout-constrained) &, - :where(.wp-site-blocks) & { - > :first-child { - margin-block-start: 0; - margin-block-end: 0; - } - // Since this appender will only ever appear on an entirely empty document, we don't account for last-child. - // This is also because it will never be the last child, the block inserter that sits in this appender is the last child. + // The following prevents user agent styles from applying margins to the appender's inner paragraph. + // This in turn prevents layout shift due to layout styles removing margins from first and last children. + margin-block-start: 0; + margin-block-end: 0; } // Dropzone. From fe2dcb6ff022b90516021a5d35a0e8bf23b7ac4b Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:24:56 +0900 Subject: [PATCH 027/605] DataViews: Tweak primary field in patterns grid layout (#66733) Co-authored-by: t-hamano Co-authored-by: youknowriad Co-authored-by: jameskoster Co-authored-by: jasmussen --- .../edit-site/src/components/page-patterns/fields.js | 11 +++-------- .../edit-site/src/components/page-patterns/style.scss | 5 ----- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/packages/edit-site/src/components/page-patterns/fields.js b/packages/edit-site/src/components/page-patterns/fields.js index 0ad47e90c20402..e016dca6cd8557 100644 --- a/packages/edit-site/src/components/page-patterns/fields.js +++ b/packages/edit-site/src/components/page-patterns/fields.js @@ -10,7 +10,7 @@ import { __experimentalHStack as HStack, Button, Tooltip, - Flex, + FlexBlock, } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { useState, useMemo, useId } from '@wordpress/element'; @@ -122,12 +122,7 @@ function TitleField( { item } ) { const title = decodeEntities( defaultGetTitle( item ) ); return ( - + { item.type === PATTERN_TYPES.theme ? ( title ) : ( @@ -142,7 +137,7 @@ function TitleField( { item } ) { { title } ) } - + { item.type === PATTERN_TYPES.theme && ( Date: Wed, 6 Nov 2024 15:10:49 +0530 Subject: [PATCH 028/605] Added toggle control to set any image as feature image if no feature image is set for post (#65896) * feature: created toggle control to set content image as feature image if no feature image is set * Fix: docs build * revert: php changes for feature image * feature: created toolbar control to set image as feature image is post don't have feature image set * feature: created block settings & added proper notices for success message * update: added required block context for feature image control * update: set feature image control as per suggestion * remove: unnessary isFeature image attribute * rename: feature image control * Fix: query loop issue * Fix: typo in set featured image * update as per suggestions * Fix: minor feedback * Fix: minor issue --- packages/block-library/src/image/block.json | 2 +- packages/block-library/src/image/image.js | 40 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index f441a6e893290b..16e31217476026 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -4,7 +4,7 @@ "name": "core/image", "title": "Image", "category": "media", - "usesContext": [ "allowResize", "imageCrop", "fixedHeight" ], + "usesContext": [ "allowResize", "imageCrop", "fixedHeight", "postId", "postType", "queryId" ], "description": "Insert an image to make a visual statement.", "keywords": [ "img", "photo", "picture" ], "textdomain": "default", diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index a8d65951635522..61baba2263989c 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -32,6 +32,7 @@ import { __experimentalUseBorderProps as useBorderProps, __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, privateApis as blockEditorPrivateApis, + BlockSettingsMenuControls, } from '@wordpress/block-editor'; import { useEffect, useMemo, useState, useRef } from '@wordpress/element'; import { __, _x, sprintf, isRTL } from '@wordpress/i18n'; @@ -39,7 +40,7 @@ import { getFilename } from '@wordpress/url'; import { getBlockBindingsSource, switchToBlockType } from '@wordpress/blocks'; import { crop, overlayText, upload, chevronDown } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; -import { store as coreStore } from '@wordpress/core-data'; +import { store as coreStore, useEntityProp } from '@wordpress/core-data'; /** * Internal dependencies @@ -893,6 +894,16 @@ export default function Image( { const shadowProps = getShadowClassesAndStyles( attributes ); const isRounded = attributes.className?.includes( 'is-style-rounded' ); + const { postType, postId, queryId } = context; + const isDescendentOfQueryLoop = Number.isFinite( queryId ); + + const [ , setFeaturedImage ] = useEntityProp( + 'postType', + postType, + 'featured_media', + postId + ); + let img = temporaryURL && hasImageErrored ? ( // Show a placeholder during upload when the blob URL can't be loaded. This can @@ -1094,10 +1105,37 @@ export default function Image( { ); } + /** + * Set the post's featured image with the current image. + */ + const setPostFeatureImage = () => { + setFeaturedImage( id ); + createSuccessNotice( __( 'Post featured image updated.' ), { + type: 'snackbar', + } ); + }; + + const featuredImageControl = ( + + { ( { selectedClientIds } ) => + selectedClientIds.length === 1 && + ! isDescendentOfQueryLoop && + postId && + id && + clientId === selectedClientIds[ 0 ] && ( + + { __( 'Set featured image' ) } + + ) + } + + ); + return ( <> { mediaReplaceFlow } { controls } + { featuredImageControl } { img } Date: Wed, 6 Nov 2024 11:10:04 +0100 Subject: [PATCH 029/605] Select Mode: Hide tool selector in the post editor and force design mode (#66784) Co-authored-by: youknowriad Co-authored-by: ntsekouras Co-authored-by: richtabor --- packages/block-editor/src/store/selectors.js | 7 +++++-- .../src/components/document-tools/index.js | 8 +++++++- .../provider/use-block-editor-settings.js | 5 +++++ .../editor/various/write-design-mode.spec.js | 20 +++++++++++++------ 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index b5afb2891ef1cf..b09c3d3d01b165 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -2764,8 +2764,11 @@ export function isNavigationMode( state ) { * @return {string} the editor mode. */ export const __unstableGetEditorMode = createRegistrySelector( - ( select ) => () => { - return select( preferencesStore ).get( 'core', 'editorTool' ); + ( select ) => ( state ) => { + return ( + state.settings.editorTool ?? + select( preferencesStore ).get( 'core', 'editorTool' ) + ); } ); diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js index b2295b555ce080..74118caaf5849c 100644 --- a/packages/editor/src/components/document-tools/index.js +++ b/packages/editor/src/components/document-tools/index.js @@ -35,6 +35,7 @@ function DocumentTools( { className, disableBlockTools = false } ) { inserterSidebarToggleRef, listViewToggleRef, showIconLabels, + showTools, } = useSelect( ( select ) => { const { get } = select( preferencesStore ); const { @@ -42,6 +43,8 @@ function DocumentTools( { className, disableBlockTools = false } ) { getEditorMode, getInserterSidebarToggleRef, getListViewToggleRef, + getRenderingMode, + getCurrentPostType, } = unlock( select( editorStore ) ); const { getShortcutRepresentation } = select( keyboardShortcutsStore ); @@ -56,6 +59,9 @@ function DocumentTools( { className, disableBlockTools = false } ) { showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), isVisualMode: getEditorMode() === 'visual', + showTools: + getRenderingMode() !== 'post-only' || + getCurrentPostType() === 'wp_template', }; }, [] ); @@ -128,7 +134,7 @@ function DocumentTools( { className, disableBlockTools = false } ) { ) } { ( isWideViewport || ! showIconLabels ) && ( <> - { isLargeViewport && ( + { showTools && isLargeViewport && ( { - test.beforeEach( async ( { admin, editor } ) => { - await admin.createNewPost(); - await expect( - editor.canvas.getByRole( 'textbox', { name: 'Add title' } ) - ).toBeFocused(); + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activateTheme( 'emptytheme' ); + } ); + + test.beforeEach( async ( { admin } ) => { + await admin.visitSiteEditor( { + postId: 'emptytheme//index', + postType: 'wp_template', + canvas: 'edit', + } ); } ); test.afterAll( async ( { requestUtils } ) => { - await requestUtils.deleteAllPosts(); + await requestUtils.activateTheme( 'twentytwentyone' ); } ); test( 'Should prevent selecting intermediary blocks', async ( { editor, page, } ) => { + // Clear all content + await editor.setContent( '' ); + // Insert a section with a nested block and an editable block. await editor.insertBlock( { name: 'core/group', From 624c938244ccd0ceda3decfcb2caa22a37487e73 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 6 Nov 2024 06:44:29 -0800 Subject: [PATCH 030/605] Delay block hydration to allow stores to initialize (#66772) Co-authored-by: westonruter Co-authored-by: michalczaplinski Co-authored-by: annezazu Co-authored-by: artemiomorales Co-authored-by: cbravobernal Co-authored-by: t-hamano --- packages/interactivity/src/init.ts | 11 +++++++++++ packages/interactivity/src/utils.ts | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/interactivity/src/init.ts b/packages/interactivity/src/init.ts index ddf6785d4dfdf4..fa1eec51c3e27e 100644 --- a/packages/interactivity/src/init.ts +++ b/packages/interactivity/src/init.ts @@ -33,6 +33,17 @@ export const init = async () => { `[data-${ directivePrefix }-interactive]` ); + /* + * This `await` with setTimeout is required to apparently ensure that the interactive blocks have their stores + * fully initialized prior to hydrating the blocks. If this is not present, then an error occurs, for example: + * > view.js:46 Uncaught (in promise) ReferenceError: Cannot access 'state' before initialization + * This occurs when splitTask() is implemented with scheduler.yield() as opposed to setTimeout(), as with the former + * split tasks are added to the front of the task queue whereas with the latter they are added to the end of the queue. + */ + await new Promise( ( resolve ) => { + setTimeout( resolve, 0 ); + } ); + for ( const node of nodes ) { if ( ! hydratedIslands.has( node ) ) { await splitTask(); diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index 9cd6f8bebb0d1c..ab6b0074727ee7 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -54,7 +54,7 @@ const afterNextFrame = ( callback: () => void ) => { /** * Returns a promise that resolves after yielding to main. * - * @return Promise + * @return Promise */ export const splitTask = typeof window.scheduler?.yield === 'function' From fc740ff4a5515e1baccd36c734e4faa6e2bafd26 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 6 Nov 2024 14:50:17 +0000 Subject: [PATCH 031/605] Bump plugin version to 19.6.0 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 128156a947b27f..89e65de4e94794 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.5 * Requires PHP: 7.2 - * Version: 19.6.0-rc.3 + * Version: 19.6.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 59fb1310a15e72..80abc752c92120 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "19.6.0-rc.3", + "version": "19.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "19.6.0-rc.3", + "version": "19.6.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "workspaces": [ diff --git a/package.json b/package.json index 6e519e2f1070e9..741475d8177705 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "19.6.0-rc.3", + "version": "19.6.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From fb9587e0dafa3f728197913c5292e0a7bae2073b Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 6 Nov 2024 15:53:11 +0000 Subject: [PATCH 032/605] Update Changelog for 19.6.0 --- changelog.txt | 299 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 297 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index ad860707a261f7..bcbfbb2dc48449 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,23 +1,318 @@ == Changelog == -= 19.6.0-rc.3 = += 19.6.0 = ## Changelog +### Enhancements + +#### Components +- Add elevation tokens to storybook. ([66122](https://github.com/WordPress/gutenberg/pull/66122)) +- Add foundations:Elevation to storybook. ([66124](https://github.com/WordPress/gutenberg/pull/66124)) +- Add radius foundation and tokens to storybook. ([66219](https://github.com/WordPress/gutenberg/pull/66219)) +- Combobox, FormTokenField: Show message when no matches found. ([66142](https://github.com/WordPress/gutenberg/pull/66142)) +- Storybook: Organizes components under 'Utilities'. ([66210](https://github.com/WordPress/gutenberg/pull/66210)) +- Tabs: Expose active tab item props, use ariakit prop types. ([66223](https://github.com/WordPress/gutenberg/pull/66223)) +- Tabs: Remove custom logic. ([66097](https://github.com/WordPress/gutenberg/pull/66097)) +- `DropdownMenuV2`: Rename to `Menu`. ([66289](https://github.com/WordPress/gutenberg/pull/66289)) + +#### Block Library +- Add Transformation from Separator to Spacer. ([66230](https://github.com/WordPress/gutenberg/pull/66230)) +- Add lightbox option in gallery block link control. ([64014](https://github.com/WordPress/gutenberg/pull/64014)) +- Archives: Add border block support. ([63400](https://github.com/WordPress/gutenberg/pull/63400)) +- Border support added to comments. ([66354](https://github.com/WordPress/gutenberg/pull/66354)) +- Cover Block: Add Image Resolution options. ([62926](https://github.com/WordPress/gutenberg/pull/62926)) +- HTML Block: Force HTML preview in view mode. ([66440](https://github.com/WordPress/gutenberg/pull/66440)) + +#### Zoom Out +- Add keyboard shortcut in editor. ([66400](https://github.com/WordPress/gutenberg/pull/66400)) +- Iterate zoom out shuffle into a more visual control. ([66194](https://github.com/WordPress/gutenberg/pull/66194)) +- Use the zoom-level value to scale the iframe. ([66280](https://github.com/WordPress/gutenberg/pull/66280)) + +#### Site Editor +- Remove synchronization of canvas mode into store. ([66213](https://github.com/WordPress/gutenberg/pull/66213)) +- Style Book: Clean up layout. ([66255](https://github.com/WordPress/gutenberg/pull/66255)) +- Update site icon and title position. ([66171](https://github.com/WordPress/gutenberg/pull/66171)) +- Editor Interface: Remove small header from global styles/plugin sidebar. ([64474](https://github.com/WordPress/gutenberg/pull/64474)) +- Remove purple coloring from DocumentBar and PostCard. ([66451](https://github.com/WordPress/gutenberg/pull/66451)) +- Core Data: Add the 'getEntitiesConfig' resolver. ([65871](https://github.com/WordPress/gutenberg/pull/65871)) +- BlockGroupToolbar: Better i18n context for toolbar labels. ([66211](https://github.com/WordPress/gutenberg/pull/66211)) +- Set `ResizableEditor` height based on border-box. ([66342](https://github.com/WordPress/gutenberg/pull/66342)) +- Add align-item related icons. ([66242](https://github.com/WordPress/gutenberg/pull/66242)) +- Stabilize isPreviewMode flag. ([66149](https://github.com/WordPress/gutenberg/pull/66149)) + +#### Data Views +- Fix alignment of action items in list layout. ([66273](https://github.com/WordPress/gutenberg/pull/66273)) +- Templates: Hide media field in list view. ([66573](https://github.com/WordPress/gutenberg/pull/66573)) + +#### Global Styles +- Backport: Caching of global styles for blocks from core. ([66349](https://github.com/WordPress/gutenberg/pull/66349)) +- Style Book: Add color tab. ([65692](https://github.com/WordPress/gutenberg/pull/65692)) + +#### Real-time Collaboration +- Inline Commenting: Disable comments on published posts for now. ([66583](https://github.com/WordPress/gutenberg/pull/66583)) + + +#### REST API +- Backport from WordPress core: Improvements for the post format query loop filter. ([66037](https://github.com/WordPress/gutenberg/pull/66037)) + +#### Build Tooling +- Create Block: Adds `--target-dir` flag to allow the tool to target where to scaffold. ([53781](https://github.com/WordPress/gutenberg/pull/53781)) +- Scripts: Add build-blocks-manifest command. ([65866](https://github.com/WordPress/gutenberg/pull/65866)) +- Scripts: Add BlueOak-1.0.0 license to GPL2 compatible. ([66139](https://github.com/WordPress/gutenberg/pull/66139)) +- WP Scripts: Add a `--root-folder` argument to the `plugin-zip` command. ([61375](https://github.com/WordPress/gutenberg/pull/61375)) + ### Bug Fixes +#### Zoom Out +- Disable zoom out toggle button when Style Book is open. ([66228](https://github.com/WordPress/gutenberg/pull/66228)) +- Don't switch editor mode when changing entities. ([66452](https://github.com/WordPress/gutenberg/pull/66452)) +- Fix scaling animation for device previews. ([66132](https://github.com/WordPress/gutenberg/pull/66132)) +- Fix zoom out not persisting while switching between editor and code editor. ([65932](https://github.com/WordPress/gutenberg/pull/65932)) +- Fix zoom out shortcut on Windows. ([66506](https://github.com/WordPress/gutenberg/pull/66506)) +- Fix: Activate zoom out on large viewport. ([66308](https://github.com/WordPress/gutenberg/pull/66308)) +- Hide slots and grouping buttons. ([66243](https://github.com/WordPress/gutenberg/pull/66243)) +- Remove zoom-out toolbar. ([66039](https://github.com/WordPress/gutenberg/pull/66039)) +- Resets the Zoom on viewed/edited entity change. ([66232](https://github.com/WordPress/gutenberg/pull/66232)) +- Revise zoom layout shift fix. ([66390](https://github.com/WordPress/gutenberg/pull/66390)) +- Zoom Out: Rely on zoom-level instead of zoom-out mode. ([66141](https://github.com/WordPress/gutenberg/pull/66141)) +- Zoom Out: Fix bouncy drop zones. ([66399](https://github.com/WordPress/gutenberg/pull/66399)) + #### Block Editor +- Block inserter: Prevent editor from crashing if `blockType.parent` is a string. ([66234](https://github.com/WordPress/gutenberg/pull/66234)) +- Block toolbar: Account for scrollable blocks that affect the position of the block toolbar. ([66188](https://github.com/WordPress/gutenberg/pull/66188)) +- Fix extra scrollbar appearing when searching in the inserter. ([66229](https://github.com/WordPress/gutenberg/pull/66229)) +- Fix: Show paragraph block variations in rich text inserter. ([66318](https://github.com/WordPress/gutenberg/pull/66318)) +- Group: Fix padding select. ([65857](https://github.com/WordPress/gutenberg/pull/65857)) +- Link Shortcut: Only trigger the link shortcut if there's a text selection. ([66056](https://github.com/WordPress/gutenberg/pull/66056)) +- Remove relative position from sidebar tabpanel. ([66267](https://github.com/WordPress/gutenberg/pull/66267)) +- Rich text: Remove empty file. ([66553](https://github.com/WordPress/gutenberg/pull/66553)) + +- Block editor: Self nesting and circular nesting block fix. ([66121](https://github.com/WordPress/gutenberg/pull/66121)) - Post Editor: Set the default value of the editorTool to edit. ([66636](https://github.com/WordPress/gutenberg/pull/66636)) +#### Components +- ColorPalette: Prevent overflow of custom color button background. ([66152](https://github.com/WordPress/gutenberg/pull/66152)) +- Fix: Text overflow in Patterns filter. ([66504](https://github.com/WordPress/gutenberg/pull/66504)) +- RadioGroup: Fix arrow key navigation in RTL. ([66202](https://github.com/WordPress/gutenberg/pull/66202)) +- Tabs and TabPanel: Fix arrow key navigation in RTL. ([66201](https://github.com/WordPress/gutenberg/pull/66201)) +- Tabs and ToggleGroupControl: Round indicator size. ([66426](https://github.com/WordPress/gutenberg/pull/66426)) +- Tabs: Fix animation timings. ([66198](https://github.com/WordPress/gutenberg/pull/66198)) +- Tabs: Override tablist's tabindex only when necessary. ([66209](https://github.com/WordPress/gutenberg/pull/66209)) +- Tabs: Restore vertical alignment for tabs content. ([66215](https://github.com/WordPress/gutenberg/pull/66215)) +- Tabs: Update indicator more reactively. ([66207](https://github.com/WordPress/gutenberg/pull/66207)) +- SpacingSizesControl: Use generic label for linked button. ([66304](https://github.com/WordPress/gutenberg/pull/66304)) + +#### i18n +- Miscellaneous i18n fixes. ([66510](https://github.com/WordPress/gutenberg/pull/66510)) +- Remove most of the occurrences of the verb toggle. ([66371](https://github.com/WordPress/gutenberg/pull/66371)) + +#### Post Editor +- Always force iframe in pattern editor. ([65887](https://github.com/WordPress/gutenberg/pull/65887)) +- Editor: Multi-entity saving: Show correct count of entities to be saved. ([66482](https://github.com/WordPress/gutenberg/pull/66482)) +- Fix : "Set featured image" button border flashes on focus. ([66092](https://github.com/WordPress/gutenberg/pull/66092)) +- Fix Parent Check Condition in `buildTermsTree`. ([66006](https://github.com/WordPress/gutenberg/pull/66006)) +- Fix: Improve the DocumentBar post type label for the Homepage and Posts Page cases. ([66355](https://github.com/WordPress/gutenberg/pull/66355)) +- PostTaxonomiesFlatTermSelector: Restore space between tag list and most used tags. ([66566](https://github.com/WordPress/gutenberg/pull/66566)) +- Typo metabox pane is a meta box panel. ([66502](https://github.com/WordPress/gutenberg/pull/66502)) + +#### Block Library +- Block registration: Normalize blockType.parent to `array`. ([66250](https://github.com/WordPress/gutenberg/pull/66250)) +- Button Block: Apply Stretch Styles Correctly. ([64770](https://github.com/WordPress/gutenberg/pull/64770)) +- Buttons: Fix the initial white space in nofollow rel. ([66303](https://github.com/WordPress/gutenberg/pull/66303)) +- Fix - Image block: Aspect ratio not responding when dimensions are not set. ([66217](https://github.com/WordPress/gutenberg/pull/66217)) +- Navigation block: Fix block appender size. ([66153](https://github.com/WordPress/gutenberg/pull/66153)) +- Fix the navigation issue inside cover blocks. ([66093](https://github.com/WordPress/gutenberg/pull/66093)) + +#### Site Editor +- Edit Site: Fix canvasMode var. ([66316](https://github.com/WordPress/gutenberg/pull/66316)) +- Fix button hover style in sidebar navigation screen. ([66505](https://github.com/WordPress/gutenberg/pull/66505)) +- Remove toggle verb from post list data views 'Toggle details panel'. ([66334](https://github.com/WordPress/gutenberg/pull/66334)) +- Revert 66431 (Site editor: Remove "default" admin CSS). ([66540](https://github.com/WordPress/gutenberg/pull/66540)) +- Restore block-library editor.css outside canvas. ([66556](https://github.com/WordPress/gutenberg/pull/66556)) +- Fix extraneous scrollbar in device previews. ([66494](https://github.com/WordPress/gutenberg/pull/66494)) +- Global styles: Move preload paths filter to 6.7 (previously 6.8). ([66543](https://github.com/WordPress/gutenberg/pull/66543)) +- Fix comment output in styles. ([66439](https://github.com/WordPress/gutenberg/pull/66439)) +- Site editor: Fix save shortcut. ([66423](https://github.com/WordPress/gutenberg/pull/66423)) +- Preload: Fix multiple regressions around global styles. ([66468](https://github.com/WordPress/gutenberg/pull/66468)) +#### Build tooling +- Create block: Ensure $slug is replaced with passed slug. ([66528](https://github.com/WordPress/gutenberg/pull/66528)) + +### Accessibility + +#### Post Editor +- Add featured image alt text. ([66189](https://github.com/WordPress/gutenberg/pull/66189)) +- Fix : Badge Color contrast. ([66360](https://github.com/WordPress/gutenberg/pull/66360)) + +#### Block Editor +- ImageSizeControls: Replace ButtonGroup with ToggleGroupControl. ([65386](https://github.com/WordPress/gutenberg/pull/65386)) +- Improve the link preview accessibility and labels. ([60908](https://github.com/WordPress/gutenberg/pull/60908)) + +#### Block Library +- Search: Replace ButtonGroup usage with ToggleGroupControl. ([65340](https://github.com/WordPress/gutenberg/pull/65340)) +- Remove clip & -webkit-clip-path for downloadable-block-list-item style.scss. ([66147](https://github.com/WordPress/gutenberg/pull/66147)) + +#### Global Styles +- Global styles menu: Avoid visible labels and accessible names mismatch. ([65124](https://github.com/WordPress/gutenberg/pull/65124)) + +#### Site Editor +- Fix Global styles panel header buttons text overlap for 'Show button text labels'. ([63243](https://github.com/WordPress/gutenberg/pull/63243)) +- Update the speak messages when switching editor modes. ([66278](https://github.com/WordPress/gutenberg/pull/66278)) + +### Performance + +#### Site Editor +- Remove "default" admin CSS. ([66431](https://github.com/WordPress/gutenberg/pull/66431)) +- Remove content styles outside canvas. ([66432](https://github.com/WordPress/gutenberg/pull/66432)) +- Block Style Variations: Reuse block metadata in WP_Theme_JSON::Get_valid_block_style_variations() for better performance. ([66539](https://github.com/WordPress/gutenberg/pull/66539)) +- Block Bindings: Use `getEntityConfig` instead of `getPostTypes` to get available slugs. ([66101](https://github.com/WordPress/gutenberg/pull/66101)) + +#### Build Tooling +- Fix/compare performance with correct branch. ([66196](https://github.com/WordPress/gutenberg/pull/66196)) + +### Experiments + +- Add Inline comment experimental flag. ([60622](https://github.com/WordPress/gutenberg/pull/60622)) +- QuickEdit: Add slug field control. ([65196](https://github.com/WordPress/gutenberg/pull/65196)) + +#### Data Views +- Quick Edit: Fix JS error when bulk editing pages. ([66358](https://github.com/WordPress/gutenberg/pull/66358)) +- QuickEdit: Add Featured Image Control. ([64496](https://github.com/WordPress/gutenberg/pull/64496)) +- QuickEdit: Add Parent field. ([66527](https://github.com/WordPress/gutenberg/pull/66527)) + +### Documentation + +- BaseControl: Auto-generate readme. ([66500](https://github.com/WordPress/gutenberg/pull/66500)) +- Components: Prevent generated readmes duplicating h1 elements when published. ([66180](https://github.com/WordPress/gutenberg/pull/66180)) +- Correct documentation on display type in flow layouts. ([66224](https://github.com/WordPress/gutenberg/pull/66224)) +- Docs: Interactivity API - Add documentation for `getServerState()` and `getServerContext()`. ([66104](https://github.com/WordPress/gutenberg/pull/66104)) +- Fix undo/redo Button size in Storybook playground. ([66538](https://github.com/WordPress/gutenberg/pull/66538)) +- Fix: JSON Schema Docgen doesn't work on Windows OS. ([66414](https://github.com/WordPress/gutenberg/pull/66414)) +- ItemGroup: Improve stories to default to bordered and separated. ([66191](https://github.com/WordPress/gutenberg/pull/66191)) +- README: Add Storybook badge. ([66529](https://github.com/WordPress/gutenberg/pull/66529)) +- Remove meetings that no longer occur from Contributor Guide. ([66421](https://github.com/WordPress/gutenberg/pull/66421)) +- Site editor routes: Add documentation for areas and prevent `edit` area from rendering when canvas is `edit`. ([66309](https://github.com/WordPress/gutenberg/pull/66309)) +- Update documentation about build process changes. ([66428](https://github.com/WordPress/gutenberg/pull/66428)) +- Docs: Example for getSelectedBlock. ([66108](https://github.com/WordPress/gutenberg/pull/66108)) + +### Code Quality + +#### Block Editor +- ESLint: Remove explicit `react-hooks/exhaustive-deps` disabling. ([66461](https://github.com/WordPress/gutenberg/pull/66461)) +- ESLint: Remove various React Compiler mutation violations. ([66327](https://github.com/WordPress/gutenberg/pull/66327)) +- Fix 'useInstanceId' hook reference. ([66406](https://github.com/WordPress/gutenberg/pull/66406)) +- Fix React Compiler error for Duotone. ([66492](https://github.com/WordPress/gutenberg/pull/66492)) +- Fix React Complier error for 'useEventListeners'. ([66495](https://github.com/WordPress/gutenberg/pull/66495)) +- Import only the actually used PostCSS exports. ([66379](https://github.com/WordPress/gutenberg/pull/66379)) +- No need to unlock public actions. ([66260](https://github.com/WordPress/gutenberg/pull/66260)) +- Remove patterns UI stylesheet from iframe. ([66306](https://github.com/WordPress/gutenberg/pull/66306)) +- Remove unnecessary Tooltip component from radius control linked button. ([66274](https://github.com/WordPress/gutenberg/pull/66274)) +- Rich Text: Fix React Complier error for 'useEventListeners'. ([66460](https://github.com/WordPress/gutenberg/pull/66460)) +- Storybook: Add BlockPatternsList story. ([66227](https://github.com/WordPress/gutenberg/pull/66227)) +- Block editor: Remove reusable blocks stylesheet from iframe. ([66285](https://github.com/WordPress/gutenberg/pull/66285)) + + +#### Components +- BorderBoxControl: Deprecate 36px default size. ([65752](https://github.com/WordPress/gutenberg/pull/65752)) +- BorderControl: Deprecate 36px default size. ([65755](https://github.com/WordPress/gutenberg/pull/65755)) +- DrodownMenuV2: Rename folder to `menu`. ([66473](https://github.com/WordPress/gutenberg/pull/66473)) +- ESLint: Stop disabling `react-hooks/exhaustive-deps` rule. ([66324](https://github.com/WordPress/gutenberg/pull/66324)) +- Fix React Compiler error for 'useAutocomplete'. ([66496](https://github.com/WordPress/gutenberg/pull/66496)) +- PaletteEdit: Use `ItemGroup` and `Item`, and avoid custom styles. ([66164](https://github.com/WordPress/gutenberg/pull/66164)) +- TabPanel: Add 40px size prop to tab Button. ([66557](https://github.com/WordPress/gutenberg/pull/66557)) +- Tabs: Align to standard compound components structure. ([66225](https://github.com/WordPress/gutenberg/pull/66225)) +- Tabs: Simplify styled components code. ([66208](https://github.com/WordPress/gutenberg/pull/66208)) + +#### Compose +- Compose: Fix React Complier error for 'useCopyToClipboard'. ([66444](https://github.com/WordPress/gutenberg/pull/66444)) +- Composer: Fix React Compiler errors for 'useDropZone'. ([66469](https://github.com/WordPress/gutenberg/pull/66469)) + +#### Dataviews +- Fields: Fix React Compiler mutation errors. ([66464](https://github.com/WordPress/gutenberg/pull/66464)) +- Fields: Update a few function definitions. ([66315](https://github.com/WordPress/gutenberg/pull/66315)) +#### Interface +- Interface: Remove duplicate 'withComplementaryAreaContext' file. ([66348](https://github.com/WordPress/gutenberg/pull/66348)) +- Interface: Remove unused private API support. ([66565](https://github.com/WordPress/gutenberg/pull/66565)) +- Interface: Use plugin context hook instead of HoC. ([66362](https://github.com/WordPress/gutenberg/pull/66362)) + +#### Zoom Out +- Bundle behavior in block-editor and add story. ([66240](https://github.com/WordPress/gutenberg/pull/66240)) +- Editor: Handle zoom out state via the 'switchEditorMode' action. ([66262](https://github.com/WordPress/gutenberg/pull/66262)) +- Fix/html scale code quality. ([66181](https://github.com/WordPress/gutenberg/pull/66181)) +- Make useZoomOut hook private. ([66374](https://github.com/WordPress/gutenberg/pull/66374)) +- Remove double click to exit hook from the block-editor package. ([66335](https://github.com/WordPress/gutenberg/pull/66335)) +- Remove viewport check from useZoomOut hook. ([66341](https://github.com/WordPress/gutenberg/pull/66341)) + +#### Post Editor +- Editor: No need to reset mode when changing device preview types. ([66261](https://github.com/WordPress/gutenberg/pull/66261)) +- Editor: Use plugin context hook in 'PluginMoreMenuItem'. ([66351](https://github.com/WordPress/gutenberg/pull/66351)) +- Editor: Use plugin context hook in 'PluginPreviewMenuItem'. ([66350](https://github.com/WordPress/gutenberg/pull/66350)) +- Fix typo after #63669. ([66396](https://github.com/WordPress/gutenberg/pull/66396)) +- Remove leftover JS code from 66451. ([66472](https://github.com/WordPress/gutenberg/pull/66472)) + +#### Block Library +- Cleanup unnecessary notice removal. ([66409](https://github.com/WordPress/gutenberg/pull/66409)) +- ESLint: Fix a couple of React Compiler reassignment errors. ([66331](https://github.com/WordPress/gutenberg/pull/66331)) +- Footnotes: Remove extra callback when parsing content. ([66370](https://github.com/WordPress/gutenberg/pull/66370)) +- Gallery: Fix React Compiler reassignment error. ([66408](https://github.com/WordPress/gutenberg/pull/66408)) +- Table Block: Remove hasArrowIndicator prop. ([66204](https://github.com/WordPress/gutenberg/pull/66204)) + +#### Style Book +- Avoid state/effect combo when generating values. ([66446](https://github.com/WordPress/gutenberg/pull/66446)) +- Fix React Compiler error. ([66445](https://github.com/WordPress/gutenberg/pull/66445)) + +#### Global Styles +- Clean up: Adjust reusable-blocks dependencies. ([66302](https://github.com/WordPress/gutenberg/pull/66302)) +- Fix React Compiler variable mutation error. ([66410](https://github.com/WordPress/gutenberg/pull/66410)) +- Theme JSON: Get_block_nodes - relocate $selectors assignment. ([66265](https://github.com/WordPress/gutenberg/pull/66265)) + +#### Interactivity API +- Interactivity API: Add comments to the `deepMerge()` function. ([66220](https://github.com/WordPress/gutenberg/pull/66220)) +- Interactivity API: Add tests for handling arrays in `deepMerge()`. ([66218](https://github.com/WordPress/gutenberg/pull/66218)) +- Interactivity API: Delay block hydration to allow interactive block stores to initialize. ([66772])(https://github.com/WordPress/gutenberg/pull/66772) + +#### Site Editor +- Prepare route registration by refactoring the site editor router. ([66030](https://github.com/WordPress/gutenberg/pull/66030)) +- Navigation: Improve trigger for fallback navigation. ([66478](https://github.com/WordPress/gutenberg/pull/66478)) +#### Build Tooling +- Scripts: Refactor to extract license logic. ([66179](https://github.com/WordPress/gutenberg/pull/66179)) +- Plugins: Deprecate 'withPluginContext' HOC. ([66363](https://github.com/WordPress/gutenberg/pull/66363)) + +### Tools + +#### Testing +- Fix [Flaky Test] Entering zoomed out mode zooms the canvas. ([66212](https://github.com/WordPress/gutenberg/pull/66212)) +- Fix end-to-end test for padding appender. ([66080](https://github.com/WordPress/gutenberg/pull/66080)) +- Upgrade Playwright to v1.48. ([66296](https://github.com/WordPress/gutenberg/pull/66296)) + +#### Build Tooling +- Add new private `vips` package. ([64845](https://github.com/WordPress/gutenberg/pull/64845)) +- Revert "Use npm workspaces for packages". ([66270](https://github.com/WordPress/gutenberg/pull/66270)) +- Add theme type to the bug report issue template. ([63851](https://github.com/WordPress/gutenberg/pull/63851)) +- Pull request automation: Use full npm install. ([66314](https://github.com/WordPress/gutenberg/pull/66314)) +- Use npm workspaces for packages (second attempt). ([66272](https://github.com/WordPress/gutenberg/pull/66272)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @AhmarZaidi: Remove toggle verb from post list data views 'Toggle details panel'. ([66334](https://github.com/WordPress/gutenberg/pull/66334)) +- @mediaformat: Remove clip & -webkit-clip-path for downloadable-block-list-item style.scss. ([66147](https://github.com/WordPress/gutenberg/pull/66147)) +- @nicolasgalvez: WP Scripts: Add a `--root-folder` argument to the `plugin-zip` command. ([61375](https://github.com/WordPress/gutenberg/pull/61375)) +- @poojabhimani12: Add Inline comment experimental flag. ([60622](https://github.com/WordPress/gutenberg/pull/60622)) +- @rinkalpagdar: Border support added to comments. ([66354](https://github.com/WordPress/gutenberg/pull/66354)) +- @rudrakshi-gupta: Add Transformation from Separator to Spacer. ([66230](https://github.com/WordPress/gutenberg/pull/66230)) +- @SH4LIN: GH-66090: Self nesting and circular nesting block fix. ([66121](https://github.com/WordPress/gutenberg/pull/66121)) ## Contributors The following contributors merged PRs in this release: -@arthur791004 +@aaronrobertshaw @afercia @AhmarZaidi @akasunil @andrewserong @arthur791004 @carolinan @cbravobernal @ciampo @dhruvang21 @ellatrix @getdave @gigitux @hbhalodia @jameskoster @jeryj @jsnajdr @juanfra @madhusudhand @MaggieCabrera @Mamaduka @mattrwalker @mcsf @mediaformat @michalczaplinski @mirka @mreishus @ndiego @nicolasgalvez @ntsekouras @oandregal @PARTHVATALIYA @peterwilsoncc @poojabhimani12 @prajapatisagar @ramonjd @renatho @rinkalpagdar @rudrakshi-gupta @ryanwelcher @SantosGuillamot @SH4LIN @sirreal @stokesman @swissspidy @t-hamano @talldan @tellthemachines @torounit @troychaplin @tyxla @vipul0425 @Vrishabhsk @vykes-mac @westonruter @youknowriad @zaguiini = 19.5.1 = From b2528743b4d8ab2a240290b09e3df9fba0523a4f Mon Sep 17 00:00:00 2001 From: Rinkal Pagdar <92097119+rinkalpagdar@users.noreply.github.com> Date: Thu, 7 Nov 2024 04:14:49 +0530 Subject: [PATCH 033/605] Post Content: Add border and spacing support (#66366) Co-authored-by: rinkalpagdar Co-authored-by: tellthemachines --- docs/reference-guides/core-blocks.md | 2 +- .../block-library/src/post-content/block.json | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index dd49d156857249..4db90e9b2f8142 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -616,7 +616,7 @@ Displays the contents of a post or page. ([Source](https://github.com/WordPress/ - **Name:** core/post-content - **Category:** theme -- **Supports:** align (full, wide), background (backgroundImage, backgroundSize), color (background, gradients, link, text), dimensions (minHeight), layout, spacing (blockGap, padding), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), background (backgroundImage, backgroundSize), color (background, gradients, link, text), dimensions (minHeight), layout, spacing (blockGap, margin, padding), typography (fontSize, lineHeight), ~~html~~ ## Date diff --git a/packages/block-library/src/post-content/block.json b/packages/block-library/src/post-content/block.json index 1b9de707cb3220..ed9c47154b2f8e 100644 --- a/packages/block-library/src/post-content/block.json +++ b/packages/block-library/src/post-content/block.json @@ -27,6 +27,7 @@ "spacing": { "blockGap": true, "padding": true, + "margin": true, "__experimentalDefaultControls": { "margin": false, "padding": false @@ -52,8 +53,20 @@ "__experimentalDefaultControls": { "fontSize": true } + }, + "__experimentalBorder": { + "radius": true, + "color": true, + "width": true, + "style": true, + "__experimentalDefaultControls": { + "radius": true, + "color": true, + "width": true, + "style": true + } } }, "style": "wp-block-post-content", "editorStyle": "wp-block-post-content-editor" -} +} \ No newline at end of file From 3e46fe7e222a20b1dff36c1a8f435436e9bc134a Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 7 Nov 2024 11:54:46 +1100 Subject: [PATCH 034/605] Global styles revisions: move focus and active state to list item (#66780) * This PR removes the button that loads the global revision and moves it up to an accessible list item. The result is that the list item is tabbable and selectable via keyboard, and the focus state wraps the entire item. * Use button border * Fix e2e tests and use composite component Unlinked contributors: jarekmorawski. Co-authored-by: ramonjd Co-authored-by: ntsekouras Co-authored-by: jasmussen --- .../screen-revisions/revisions-buttons.js | 59 +++++++++---------- .../global-styles/screen-revisions/style.scss | 28 ++++----- .../block-style-variations.spec.js | 2 +- .../user-global-styles-revisions.spec.js | 18 +++--- 4 files changed, 50 insertions(+), 57 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js index b7ce38857c9461..d6acd711108b5c 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js @@ -1,18 +1,13 @@ -/** - * External dependencies - */ -import clsx from 'clsx'; - /** * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Button } from '@wordpress/components'; +import { Button, Composite } from '@wordpress/components'; import { dateI18n, getDate, humanTimeDiff, getSettings } from '@wordpress/date'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; - +import { ENTER, SPACE } from '@wordpress/keycodes'; /** * Internal dependencies */ @@ -117,10 +112,11 @@ function RevisionsButtons( { const { datetimeAbbreviated } = getSettings().formats; return ( -
    { userRevisions.map( ( revision, index ) => { const { id, author, modified } = revision; @@ -149,28 +145,26 @@ function RevisionsButtons( { ); return ( -
  1. - + { isSelected && ( areStylesEqual ? (

    @@ -225,16 +219,19 @@ function RevisionsButtons( { variant="primary" className="edit-site-global-styles-screen-revisions__apply-button" onClick={ onApplyRevision } + aria-label={ __( + 'Apply the selected revision to your site.' + ) } > { isReset ? __( 'Reset to defaults' ) : __( 'Apply' ) } ) ) } -

  2. + ); } ) } -
+ ); } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/style.scss b/packages/edit-site/src/components/global-styles/screen-revisions/style.scss index 5d0e7cb185137a..1773d1a179b3dd 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/style.scss +++ b/packages/edit-site/src/components/global-styles/screen-revisions/style.scss @@ -14,6 +14,11 @@ display: flex; flex-direction: column; + &[role="option"]:active, + &[role="option"]:focus { + @include button-style__focus(); + } + &:hover { background: rgba(var(--wp-admin-theme-color--rgb), 0.04); .edit-site-global-styles-screen-revisions__date { @@ -42,7 +47,7 @@ border: 4px solid transparent; } - &.is-selected { + &[aria-selected="true"] { border-radius: $radius-small; // Only visible in Windows High Contrast mode. @@ -52,10 +57,6 @@ color: var(--wp-admin-theme-color); background: rgba(var(--wp-admin-theme-color--rgb), 0.04); - .edit-site-global-styles-screen-revisions__revision-button { - opacity: 1; - } - .edit-site-global-styles-screen-revisions__date { color: var(--wp-admin-theme-color); } @@ -86,23 +87,16 @@ &:last-child::after { height: $grid-unit-20 + 2; } - - // Nested to override specificity of .components-button. - .edit-site-global-styles-screen-revisions__revision-button { - width: 100%; - height: auto; - display: block; - padding: $grid-unit-15 $grid-unit-15 $grid-unit-05 $grid-unit-50; - z-index: 1; - position: relative; - outline-offset: -2px; - } +} +.edit-site-global-styles-screen-revisions__revision-item-wrapper { + display: block; + padding: $grid-unit-15 $grid-unit-15 $grid-unit-05 $grid-unit-50; } .edit-site-global-styles-screen-revisions__apply-button.is-primary, .edit-site-global-styles-screen-revisions__applied-text { align-self: flex-start; - // Left margin = left padding of .edit-site-global-styles-screen-revisions__revision-button. + // Left margin = left padding of .edit-site-global-styles-screen-revisions__revision-item-wrapper. margin: $grid-unit-05 $grid-unit-15 $grid-unit-15 $grid-unit-50; } diff --git a/test/e2e/specs/site-editor/block-style-variations.spec.js b/test/e2e/specs/site-editor/block-style-variations.spec.js index 3c642ea9d1299e..03fc5398f4a0a5 100644 --- a/test/e2e/specs/site-editor/block-style-variations.spec.js +++ b/test/e2e/specs/site-editor/block-style-variations.spec.js @@ -245,7 +245,7 @@ test.describe( 'Block Style Variations', () => { // Click on previous revision. await page - .getByRole( 'button', { + .getByRole( 'option', { name: /^Changes saved by /, } ) .nth( 1 ) diff --git a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js index e0b00fe1e59460..91142d5395640c 100644 --- a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js +++ b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js @@ -50,7 +50,7 @@ test.describe( 'Style Revisions', () => { // Now there should be enough revisions to show the revisions UI. await page.getByRole( 'button', { name: 'Revisions' } ).click(); - const revisionButtons = page.getByRole( 'button', { + const revisionButtons = page.getByRole( 'option', { name: /^Changes saved by /, } ); @@ -83,14 +83,14 @@ test.describe( 'Style Revisions', () => { await page.getByRole( 'button', { name: 'Revisions' } ).click(); - const unSavedButton = page.getByRole( 'button', { + const unSavedButton = page.getByRole( 'option', { name: /^Unsaved changes/, } ); await expect( unSavedButton ).toBeVisible(); await page - .getByRole( 'button', { name: /^Changes saved by / } ) + .getByRole( 'option', { name: /^Changes saved by / } ) .last() .click(); @@ -118,14 +118,16 @@ test.describe( 'Style Revisions', () => { await editor.canvas.locator( 'body' ).click(); await userGlobalStylesRevisions.openStylesPanel(); await page.getByRole( 'button', { name: 'Revisions' } ).click(); - const lastRevisionButton = page + const lastRevisionItem = page .getByLabel( 'Global styles revisions list' ) - .getByRole( 'button' ) + .getByRole( 'option' ) .last(); - await expect( lastRevisionButton ).toContainText( 'Default styles' ); - await lastRevisionButton.click(); + await expect( lastRevisionItem ).toContainText( 'Default styles' ); + await lastRevisionItem.click(); await expect( - page.getByRole( 'button', { name: 'Reset to defaults' } ) + page.getByRole( 'button', { + name: 'Apply the selected revision to your site.', + } ) ).toBeVisible(); } ); From 4651b8235d5220f02b1133266995f32c34fc9122 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 7 Nov 2024 11:06:11 +1000 Subject: [PATCH 035/605] Block Gap: Fix block spacing control for axial gap supported blocks (#66783) Co-authored-by: aaronrobertshaw Co-authored-by: andrewserong Co-authored-by: t-hamano Co-authored-by: ramonjd Co-authored-by: getdave Co-authored-by: carolinan Co-authored-by: ndiego Co-authored-by: juanfra Co-authored-by: ltrihan --- .../global-styles/dimensions-panel.js | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/dimensions-panel.js b/packages/block-editor/src/components/global-styles/dimensions-panel.js index 4c52de6a3d7d11..c19788ebfcb580 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -147,24 +147,30 @@ function splitStyleValue( value ) { return value; } -function splitGapValue( value ) { - // Check for shorthand value (a string value). - if ( value && typeof value === 'string' ) { - // If the value is a string, treat it as a single side (top) for the spacing controls. - return { - top: value, - }; +function splitGapValue( value, isAxialGap ) { + if ( ! value ) { + return value; } - if ( value ) { - return { - ...value, - right: value?.left, - bottom: value?.top, - }; + // Check for shorthand value (a string value). + if ( typeof value === 'string' ) { + /* + * Map the string value to appropriate sides for the spacing control depending + * on whether the current block has axial gap support or not. + * + * Note: The axial value pairs must match for the spacing control to display + * the appropriate horizontal/vertical sliders. + */ + return isAxialGap + ? { top: value, right: value, bottom: value, left: value } + : { top: value }; } - return value; + return { + ...value, + right: value?.left, + bottom: value?.top, + }; } function DimensionsToolsPanel( { @@ -325,13 +331,13 @@ export default function DimensionsPanel( { // Block Gap const showGapControl = useHasGap( settings ); - const gapValue = decodeValue( inheritedValue?.spacing?.blockGap ); - const gapValues = splitGapValue( gapValue ); const gapSides = Array.isArray( settings?.spacing?.blockGap ) ? settings?.spacing?.blockGap : settings?.spacing?.blockGap?.sides; const isAxialGap = gapSides && gapSides.some( ( side ) => AXIAL_SIDES.includes( side ) ); + const gapValue = decodeValue( inheritedValue?.spacing?.blockGap ); + const gapValues = splitGapValue( gapValue, isAxialGap ); const setGapValue = ( newGapValue ) => { onChange( setImmutably( value, [ 'spacing', 'blockGap' ], newGapValue ) From a0b0e5f00e785c5d8a7f6c4e0ddd0edf268f7dbf Mon Sep 17 00:00:00 2001 From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:59:46 +0530 Subject: [PATCH 036/605] Group the storybook components in feedback category (#66660) Co-authored-by: hbhalodia Co-authored-by: tyxla --- packages/components/src/notice/stories/index.story.tsx | 3 ++- packages/components/src/progress-bar/stories/index.story.tsx | 3 ++- packages/components/src/snackbar/stories/index.story.tsx | 3 ++- packages/components/src/snackbar/stories/list.story.tsx | 3 ++- packages/components/src/spinner/stories/index.story.tsx | 3 ++- packages/components/src/tip/stories/index.story.tsx | 3 ++- storybook/preview.js | 1 + 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/components/src/notice/stories/index.story.tsx b/packages/components/src/notice/stories/index.story.tsx index 95bf82016c2b04..042b4ac3ec79b3 100644 --- a/packages/components/src/notice/stories/index.story.tsx +++ b/packages/components/src/notice/stories/index.story.tsx @@ -17,7 +17,8 @@ import NoticeList from '../list'; import type { NoticeListProps } from '../types'; const meta: Meta< typeof Notice > = { - title: 'Components/Notice', + title: 'Components/Feedback/Notice', + id: 'components-notice', component: Notice, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 subcomponents: { NoticeList }, diff --git a/packages/components/src/progress-bar/stories/index.story.tsx b/packages/components/src/progress-bar/stories/index.story.tsx index 4396c4cc48eebb..2f6bb4dbe000fd 100644 --- a/packages/components/src/progress-bar/stories/index.story.tsx +++ b/packages/components/src/progress-bar/stories/index.story.tsx @@ -10,7 +10,8 @@ import { ProgressBar } from '..'; const meta: Meta< typeof ProgressBar > = { component: ProgressBar, - title: 'Components/ProgressBar', + title: 'Components/Feedback/ProgressBar', + id: 'components-progressbar', argTypes: { value: { control: { type: 'number', min: 0, max: 100, step: 1 } }, }, diff --git a/packages/components/src/snackbar/stories/index.story.tsx b/packages/components/src/snackbar/stories/index.story.tsx index c1f525b60b3919..9bd1dae42b71be 100644 --- a/packages/components/src/snackbar/stories/index.story.tsx +++ b/packages/components/src/snackbar/stories/index.story.tsx @@ -15,7 +15,8 @@ import Icon from '../../icon'; import Snackbar from '..'; const meta: Meta< typeof Snackbar > = { - title: 'Components/Snackbar', + title: 'Components/Feedback/Snackbar', + id: 'components-snackbar', component: Snackbar, argTypes: { as: { control: { type: null } }, diff --git a/packages/components/src/snackbar/stories/list.story.tsx b/packages/components/src/snackbar/stories/list.story.tsx index 8548ffba1e1f09..1f93f374ec1745 100644 --- a/packages/components/src/snackbar/stories/list.story.tsx +++ b/packages/components/src/snackbar/stories/list.story.tsx @@ -14,7 +14,8 @@ import { useState } from '@wordpress/element'; import SnackbarList from '../list'; const meta: Meta< typeof SnackbarList > = { - title: 'Components/SnackbarList', + title: 'Components/Feedback/SnackbarList', + id: 'components-snackbarlist', component: SnackbarList, argTypes: { as: { control: { type: null } }, diff --git a/packages/components/src/spinner/stories/index.story.tsx b/packages/components/src/spinner/stories/index.story.tsx index 1062c6406bfb4e..1dc5879ac41444 100644 --- a/packages/components/src/spinner/stories/index.story.tsx +++ b/packages/components/src/spinner/stories/index.story.tsx @@ -10,7 +10,8 @@ import Spinner from '../'; import { space } from '../../utils/space'; const meta: Meta< typeof Spinner > = { - title: 'Components/Spinner', + title: 'Components/Feedback/Spinner', + id: 'components-spinner', component: Spinner, parameters: { controls: { diff --git a/packages/components/src/tip/stories/index.story.tsx b/packages/components/src/tip/stories/index.story.tsx index 3999c6b9be45f4..3026f2a58fae73 100644 --- a/packages/components/src/tip/stories/index.story.tsx +++ b/packages/components/src/tip/stories/index.story.tsx @@ -10,7 +10,8 @@ import Tip from '..'; const meta: Meta< typeof Tip > = { component: Tip, - title: 'Components/Tip', + title: 'Components/Feedback/Tip', + id: 'components-tip', argTypes: { children: { control: { type: 'text' } }, }, diff --git a/storybook/preview.js b/storybook/preview.js index 21f2eab912059a..7672e7ebdcdff2 100644 --- a/storybook/preview.js +++ b/storybook/preview.js @@ -132,6 +132,7 @@ export const parameters = { 'Contributing Guidelines', 'Actions', 'Containers', + 'Feedback', 'Utilities', ], 'Components (Experimental)', From 88cf9bd905f00d8967bfe185176afc2892ebae70 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Thu, 7 Nov 2024 12:50:42 +0800 Subject: [PATCH 037/605] Revert "Set image width to `fit-content` to solve aspect ratio problems in Firefox. (#66217)" (#66804) Co-authored-by: kevin940726 Co-authored-by: ramonjd Co-authored-by: mukeshpanchal27 Co-authored-by: juanfra This reverts commit 0e65adcbe187797a536aa99f56b063a61cafdc3d. --- packages/block-library/src/image/style.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 79689e82006cdc..1bb19bf29da691 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -10,10 +10,6 @@ vertical-align: bottom; box-sizing: border-box; - &:not([src$=".svg"]) { - width: fit-content; - } - @media (prefers-reduced-motion: no-preference) { &.hide { visibility: hidden; From 20b773b7a47bc80aa7516a6955f23eb24782922f Mon Sep 17 00:00:00 2001 From: Hit Bhalodia <58802366+hbhalodia@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:58:27 +0530 Subject: [PATCH 038/605] Feat: Storybook: Improve component organisation - Navigation Category - Issue #66275 (#66658) * Group the storybook components in Navigaiton category * Fix tabs to add inside containers and add experimental id for component * Fix the syntax style Co-authored-by: hbhalodia Co-authored-by: tyxla --- packages/components/src/external-link/stories/index.story.tsx | 3 ++- packages/components/src/navigator/stories/index.story.tsx | 3 ++- packages/components/src/tree-grid/stories/index.story.tsx | 3 ++- storybook/preview.js | 2 ++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/components/src/external-link/stories/index.story.tsx b/packages/components/src/external-link/stories/index.story.tsx index 91131e0c88aab0..eb70999413187f 100644 --- a/packages/components/src/external-link/stories/index.story.tsx +++ b/packages/components/src/external-link/stories/index.story.tsx @@ -10,7 +10,8 @@ import ExternalLink from '..'; const meta: Meta< typeof ExternalLink > = { component: ExternalLink, - title: 'Components/ExternalLink', + title: 'Components/Navigation/ExternalLink', + id: 'components-externallink', argTypes: { children: { control: { type: 'text' } }, }, diff --git a/packages/components/src/navigator/stories/index.story.tsx b/packages/components/src/navigator/stories/index.story.tsx index e9e342bb0d2eee..bd2cdc17a1263c 100644 --- a/packages/components/src/navigator/stories/index.story.tsx +++ b/packages/components/src/navigator/stories/index.story.tsx @@ -21,7 +21,8 @@ const meta: Meta< typeof Navigator > = { // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 BackButton: Navigator.BackButton, }, - title: 'Components/Navigator', + title: 'Components/Navigation/Navigator', + id: 'components-navigator', argTypes: { as: { control: { type: null } }, children: { control: { type: null } }, diff --git a/packages/components/src/tree-grid/stories/index.story.tsx b/packages/components/src/tree-grid/stories/index.story.tsx index 44af154c685b2f..5a1ed95e1fd627 100644 --- a/packages/components/src/tree-grid/stories/index.story.tsx +++ b/packages/components/src/tree-grid/stories/index.story.tsx @@ -16,7 +16,8 @@ import { Button } from '../../button'; import InputControl from '../../input-control'; const meta: Meta< typeof TreeGrid > = { - title: 'Components (Experimental)/TreeGrid', + title: 'Components (Experimental)/Navigation/TreeGrid', + id: 'components-experimental-treegrid', component: TreeGrid, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 subcomponents: { TreeGridRow, TreeGridCell }, diff --git a/storybook/preview.js b/storybook/preview.js index 7672e7ebdcdff2..9e9dd587b39c4c 100644 --- a/storybook/preview.js +++ b/storybook/preview.js @@ -133,9 +133,11 @@ export const parameters = { 'Actions', 'Containers', 'Feedback', + 'Navigation', 'Utilities', ], 'Components (Experimental)', + [ 'Navigation' ], 'Icons', ], }, From b9b1a52d967db7a014100dd7d49a403ac26b7238 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Thu, 7 Nov 2024 09:35:45 +0200 Subject: [PATCH 039/605] =?UTF-8?q?Relocate=20=E2=80=9CView=E2=80=9D=20ext?= =?UTF-8?q?ernal=20link=20to=20end=20of=20editor=20header=20controls=20(#6?= =?UTF-8?q?6785)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ntsekouras Co-authored-by: draganescu Co-authored-by: jasmussen Co-authored-by: richtabor --- packages/editor/src/components/header/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/components/header/index.js b/packages/editor/src/components/header/index.js index 07a5e9ddee5911..5a9cc2f94a5951 100644 --- a/packages/editor/src/components/header/index.js +++ b/packages/editor/src/components/header/index.js @@ -156,19 +156,21 @@ function Header( { ) } - { canBeZoomedOut && isEditorIframed && isWideViewport && ( - - ) } + + - + + { canBeZoomedOut && isEditorIframed && isWideViewport && ( + + ) } { ( isWideViewport || ! showIconLabels ) && ( From 8be8e4675637726b0ff3194ccc547307c4a489bc Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:26:40 +0900 Subject: [PATCH 040/605] ESLint: Fix React Compiler violations in various commands (#66787) Co-authored-by: tyxla Co-authored-by: Mamaduka --- .../components/use-block-commands/index.js | 479 +++++++++-------- .../src/admin-navigation-commands.js | 129 ++--- .../src/site-editor-navigation-commands.js | 256 ++++----- .../src/hooks/commands/use-common-commands.js | 499 +++++++++--------- .../hooks/commands/use-edit-mode-commands.js | 259 ++++----- .../editor/src/components/commands/index.js | 472 +++++++++-------- 6 files changed, 1069 insertions(+), 1025 deletions(-) diff --git a/packages/block-editor/src/components/use-block-commands/index.js b/packages/block-editor/src/components/use-block-commands/index.js index e739729c8f9e84..ff919710a2284e 100644 --- a/packages/block-editor/src/components/use-block-commands/index.js +++ b/packages/block-editor/src/components/use-block-commands/index.js @@ -24,275 +24,286 @@ import { import BlockIcon from '../block-icon'; import { store as blockEditorStore } from '../../store'; -export const useTransformCommands = () => { - const { replaceBlocks, multiSelect } = useDispatch( blockEditorStore ); - const { - blocks, - clientIds, - canRemove, - possibleBlockTransformations, - invalidSelection, - } = useSelect( ( select ) => { +const getTransformCommands = () => + function useTransformCommands() { + const { replaceBlocks, multiSelect } = useDispatch( blockEditorStore ); const { - getBlockRootClientId, - getBlockTransformItems, - getSelectedBlockClientIds, - getBlocksByClientId, - canRemoveBlocks, - } = select( blockEditorStore ); + blocks, + clientIds, + canRemove, + possibleBlockTransformations, + invalidSelection, + } = useSelect( ( select ) => { + const { + getBlockRootClientId, + getBlockTransformItems, + getSelectedBlockClientIds, + getBlocksByClientId, + canRemoveBlocks, + } = select( blockEditorStore ); + + const selectedBlockClientIds = getSelectedBlockClientIds(); + const selectedBlocks = getBlocksByClientId( + selectedBlockClientIds + ); + + // selectedBlocks can have `null`s when something tries to call `selectBlock` with an inexistent clientId. + // These nulls will cause fatal errors down the line. + // In order to prevent discrepancies between selectedBlockClientIds and selectedBlocks, we effectively treat the entire selection as invalid. + // @see https://github.com/WordPress/gutenberg/pull/59410#issuecomment-2006304536 + if ( selectedBlocks.filter( ( block ) => ! block ).length > 0 ) { + return { + invalidSelection: true, + }; + } - const selectedBlockClientIds = getSelectedBlockClientIds(); - const selectedBlocks = getBlocksByClientId( selectedBlockClientIds ); + const rootClientId = getBlockRootClientId( + selectedBlockClientIds[ 0 ] + ); + return { + blocks: selectedBlocks, + clientIds: selectedBlockClientIds, + possibleBlockTransformations: getBlockTransformItems( + selectedBlocks, + rootClientId + ), + canRemove: canRemoveBlocks( selectedBlockClientIds ), + invalidSelection: false, + }; + }, [] ); - // selectedBlocks can have `null`s when something tries to call `selectBlock` with an inexistent clientId. - // These nulls will cause fatal errors down the line. - // In order to prevent discrepancies between selectedBlockClientIds and selectedBlocks, we effectively treat the entire selection as invalid. - // @see https://github.com/WordPress/gutenberg/pull/59410#issuecomment-2006304536 - if ( selectedBlocks.filter( ( block ) => ! block ).length > 0 ) { + if ( invalidSelection ) { return { - invalidSelection: true, + isLoading: false, + commands: [], }; } + const isTemplate = blocks.length === 1 && isTemplatePart( blocks[ 0 ] ); + + function selectForMultipleBlocks( insertedBlocks ) { + if ( insertedBlocks.length > 1 ) { + multiSelect( + insertedBlocks[ 0 ].clientId, + insertedBlocks[ insertedBlocks.length - 1 ].clientId + ); + } + } - const rootClientId = getBlockRootClientId( - selectedBlockClientIds[ 0 ] - ); - return { - blocks: selectedBlocks, - clientIds: selectedBlockClientIds, - possibleBlockTransformations: getBlockTransformItems( - selectedBlocks, - rootClientId - ), - canRemove: canRemoveBlocks( selectedBlockClientIds ), - invalidSelection: false, - }; - }, [] ); + // Simple block tranformation based on the `Block Transforms` API. + function onBlockTransform( name ) { + const newBlocks = switchToBlockType( blocks, name ); + replaceBlocks( clientIds, newBlocks ); + selectForMultipleBlocks( newBlocks ); + } - if ( invalidSelection ) { - return { - isLoading: false, - commands: [], - }; - } - const isTemplate = blocks.length === 1 && isTemplatePart( blocks[ 0 ] ); - - function selectForMultipleBlocks( insertedBlocks ) { - if ( insertedBlocks.length > 1 ) { - multiSelect( - insertedBlocks[ 0 ].clientId, - insertedBlocks[ insertedBlocks.length - 1 ].clientId - ); + /** + * The `isTemplate` check is a stopgap solution here. + * Ideally, the Transforms API should handle this + * by allowing to exclude blocks from wildcard transformations. + */ + const hasPossibleBlockTransformations = + !! possibleBlockTransformations.length && canRemove && ! isTemplate; + + if ( + ! clientIds || + clientIds.length < 1 || + ! hasPossibleBlockTransformations + ) { + return { isLoading: false, commands: [] }; } - } - - // Simple block tranformation based on the `Block Transforms` API. - function onBlockTransform( name ) { - const newBlocks = switchToBlockType( blocks, name ); - replaceBlocks( clientIds, newBlocks ); - selectForMultipleBlocks( newBlocks ); - } - - /** - * The `isTemplate` check is a stopgap solution here. - * Ideally, the Transforms API should handle this - * by allowing to exclude blocks from wildcard transformations. - */ - const hasPossibleBlockTransformations = - !! possibleBlockTransformations.length && canRemove && ! isTemplate; - - if ( - ! clientIds || - clientIds.length < 1 || - ! hasPossibleBlockTransformations - ) { - return { isLoading: false, commands: [] }; - } - - const commands = possibleBlockTransformations.map( ( transformation ) => { - const { name, title, icon } = transformation; - return { - name: 'core/block-editor/transform-to-' + name.replace( '/', '-' ), - /* translators: %s: Block or block variation name. */ - label: sprintf( __( 'Transform to %s' ), title ), - icon: , - callback: ( { close } ) => { - onBlockTransform( name ); - close(); + + const commands = possibleBlockTransformations.map( + ( transformation ) => { + const { name, title, icon } = transformation; + return { + name: + 'core/block-editor/transform-to-' + + name.replace( '/', '-' ), + /* translators: %s: Block or block variation name. */ + label: sprintf( __( 'Transform to %s' ), title ), + icon: , + callback: ( { close } ) => { + onBlockTransform( name ); + close(); + }, + }; + } + ); + + return { isLoading: false, commands }; + }; + +const getQuickActionsCommands = () => + function useQuickActionsCommands() { + const { clientIds, isUngroupable, isGroupable } = useSelect( + ( select ) => { + const { + getSelectedBlockClientIds, + isUngroupable: _isUngroupable, + isGroupable: _isGroupable, + } = select( blockEditorStore ); + const selectedBlockClientIds = getSelectedBlockClientIds(); + + return { + clientIds: selectedBlockClientIds, + isUngroupable: _isUngroupable(), + isGroupable: _isGroupable(), + }; }, - }; - } ); + [] + ); + const { + canInsertBlockType, + getBlockRootClientId, + getBlocksByClientId, + canRemoveBlocks, + } = useSelect( blockEditorStore ); + const { getDefaultBlockName, getGroupingBlockName } = + useSelect( blocksStore ); - return { isLoading: false, commands }; -}; + const blocks = getBlocksByClientId( clientIds ); -const useQuickActionsCommands = () => { - const { clientIds, isUngroupable, isGroupable } = useSelect( ( select ) => { const { - getSelectedBlockClientIds, - isUngroupable: _isUngroupable, - isGroupable: _isGroupable, - } = select( blockEditorStore ); - const selectedBlockClientIds = getSelectedBlockClientIds(); + removeBlocks, + replaceBlocks, + duplicateBlocks, + insertAfterBlock, + insertBeforeBlock, + } = useDispatch( blockEditorStore ); + + const onGroup = () => { + if ( ! blocks.length ) { + return; + } - return { - clientIds: selectedBlockClientIds, - isUngroupable: _isUngroupable(), - isGroupable: _isGroupable(), + const groupingBlockName = getGroupingBlockName(); + + // Activate the `transform` on `core/group` which does the conversion. + const newBlocks = switchToBlockType( blocks, groupingBlockName ); + + if ( ! newBlocks ) { + return; + } + replaceBlocks( clientIds, newBlocks ); }; - }, [] ); - const { - canInsertBlockType, - getBlockRootClientId, - getBlocksByClientId, - canRemoveBlocks, - } = useSelect( blockEditorStore ); - const { getDefaultBlockName, getGroupingBlockName } = - useSelect( blocksStore ); - - const blocks = getBlocksByClientId( clientIds ); - - const { - removeBlocks, - replaceBlocks, - duplicateBlocks, - insertAfterBlock, - insertBeforeBlock, - } = useDispatch( blockEditorStore ); - - const onGroup = () => { - if ( ! blocks.length ) { - return; - } + const onUngroup = () => { + if ( ! blocks.length ) { + return; + } - const groupingBlockName = getGroupingBlockName(); + const innerBlocks = blocks[ 0 ].innerBlocks; - // Activate the `transform` on `core/group` which does the conversion. - const newBlocks = switchToBlockType( blocks, groupingBlockName ); + if ( ! innerBlocks.length ) { + return; + } - if ( ! newBlocks ) { - return; - } - replaceBlocks( clientIds, newBlocks ); - }; - const onUngroup = () => { - if ( ! blocks.length ) { - return; + replaceBlocks( clientIds, innerBlocks ); + }; + + if ( ! clientIds || clientIds.length < 1 ) { + return { isLoading: false, commands: [] }; } - const innerBlocks = blocks[ 0 ].innerBlocks; + const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); + const canInsertDefaultBlock = canInsertBlockType( + getDefaultBlockName(), + rootClientId + ); + const canDuplicate = blocks.every( ( block ) => { + return ( + !! block && + hasBlockSupport( block.name, 'multiple', true ) && + canInsertBlockType( block.name, rootClientId ) + ); + } ); + const canRemove = canRemoveBlocks( clientIds ); - if ( ! innerBlocks.length ) { - return; + const commands = []; + + if ( canDuplicate ) { + commands.push( { + name: 'duplicate', + label: __( 'Duplicate' ), + callback: () => duplicateBlocks( clientIds, true ), + icon: copy, + } ); } - replaceBlocks( clientIds, innerBlocks ); - }; + if ( canInsertDefaultBlock ) { + commands.push( + { + name: 'add-before', + label: __( 'Add before' ), + callback: () => { + const clientId = Array.isArray( clientIds ) + ? clientIds[ 0 ] + : clientId; + insertBeforeBlock( clientId ); + }, + icon: add, + }, + { + name: 'add-after', + label: __( 'Add after' ), + callback: () => { + const clientId = Array.isArray( clientIds ) + ? clientIds[ clientIds.length - 1 ] + : clientId; + insertAfterBlock( clientId ); + }, + icon: add, + } + ); + } - if ( ! clientIds || clientIds.length < 1 ) { - return { isLoading: false, commands: [] }; - } - - const rootClientId = getBlockRootClientId( clientIds[ 0 ] ); - const canInsertDefaultBlock = canInsertBlockType( - getDefaultBlockName(), - rootClientId - ); - const canDuplicate = blocks.every( ( block ) => { - return ( - !! block && - hasBlockSupport( block.name, 'multiple', true ) && - canInsertBlockType( block.name, rootClientId ) - ); - } ); - const canRemove = canRemoveBlocks( clientIds ); + if ( isGroupable ) { + commands.push( { + name: 'Group', + label: __( 'Group' ), + callback: onGroup, + icon: group, + } ); + } - const commands = []; + if ( isUngroupable ) { + commands.push( { + name: 'ungroup', + label: __( 'Ungroup' ), + callback: onUngroup, + icon: ungroup, + } ); + } - if ( canDuplicate ) { - commands.push( { - name: 'duplicate', - label: __( 'Duplicate' ), - callback: () => duplicateBlocks( clientIds, true ), - icon: copy, - } ); - } - - if ( canInsertDefaultBlock ) { - commands.push( - { - name: 'add-before', - label: __( 'Add before' ), - callback: () => { - const clientId = Array.isArray( clientIds ) - ? clientIds[ 0 ] - : clientId; - insertBeforeBlock( clientId ); - }, - icon: add, - }, - { - name: 'add-after', - label: __( 'Add after' ), - callback: () => { - const clientId = Array.isArray( clientIds ) - ? clientIds[ clientIds.length - 1 ] - : clientId; - insertAfterBlock( clientId ); + if ( canRemove ) { + commands.push( { + name: 'remove', + label: __( 'Delete' ), + callback: () => removeBlocks( clientIds, true ), + icon: remove, + } ); + } + + return { + isLoading: false, + commands: commands.map( ( command ) => ( { + ...command, + name: 'core/block-editor/action-' + command.name, + callback: ( { close } ) => { + command.callback(); + close(); }, - icon: add, - } - ); - } - - if ( isGroupable ) { - commands.push( { - name: 'Group', - label: __( 'Group' ), - callback: onGroup, - icon: group, - } ); - } - - if ( isUngroupable ) { - commands.push( { - name: 'ungroup', - label: __( 'Ungroup' ), - callback: onUngroup, - icon: ungroup, - } ); - } - - if ( canRemove ) { - commands.push( { - name: 'remove', - label: __( 'Delete' ), - callback: () => removeBlocks( clientIds, true ), - icon: remove, - } ); - } - - return { - isLoading: false, - commands: commands.map( ( command ) => ( { - ...command, - name: 'core/block-editor/action-' + command.name, - callback: ( { close } ) => { - command.callback(); - close(); - }, - } ) ), + } ) ), + }; }; -}; export const useBlockCommands = () => { useCommandLoader( { name: 'core/block-editor/blockTransforms', - hook: useTransformCommands, + hook: getTransformCommands(), } ); useCommandLoader( { name: 'core/block-editor/blockQuickActions', - hook: useQuickActionsCommands, + hook: getQuickActionsCommands(), context: 'block-selection-edit', } ); }; diff --git a/packages/core-commands/src/admin-navigation-commands.js b/packages/core-commands/src/admin-navigation-commands.js index c0d8bb084b46ad..9f0883faa3c17a 100644 --- a/packages/core-commands/src/admin-navigation-commands.js +++ b/packages/core-commands/src/admin-navigation-commands.js @@ -18,75 +18,78 @@ import { unlock } from './lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); -function useAddNewPageCommand() { - const isSiteEditor = getPath( window.location.href )?.includes( - 'site-editor.php' - ); - const history = useHistory(); - const isBlockBasedTheme = useSelect( ( select ) => { - return select( coreStore ).getCurrentTheme()?.is_block_theme; - }, [] ); - const { saveEntityRecord } = useDispatch( coreStore ); - const { createErrorNotice } = useDispatch( noticesStore ); +const getAddNewPageCommand = () => + function useAddNewPageCommand() { + const isSiteEditor = getPath( window.location.href )?.includes( + 'site-editor.php' + ); + const history = useHistory(); + const isBlockBasedTheme = useSelect( ( select ) => { + return select( coreStore ).getCurrentTheme()?.is_block_theme; + }, [] ); + const { saveEntityRecord } = useDispatch( coreStore ); + const { createErrorNotice } = useDispatch( noticesStore ); - const createPageEntity = useCallback( - async ( { close } ) => { - try { - const page = await saveEntityRecord( - 'postType', - 'page', - { - status: 'draft', - }, - { - throwOnError: true, + const createPageEntity = useCallback( + async ( { close } ) => { + try { + const page = await saveEntityRecord( + 'postType', + 'page', + { + status: 'draft', + }, + { + throwOnError: true, + } + ); + if ( page?.id ) { + history.push( { + postId: page.id, + postType: 'page', + canvas: 'edit', + } ); } - ); - if ( page?.id ) { - history.push( { - postId: page.id, - postType: 'page', - canvas: 'edit', + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( + 'An error occurred while creating the item.' + ); + + createErrorNotice( errorMessage, { + type: 'snackbar', } ); + } finally { + close(); } - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while creating the item.' ); - - createErrorNotice( errorMessage, { - type: 'snackbar', - } ); - } finally { - close(); - } - }, - [ createErrorNotice, history, saveEntityRecord ] - ); - - const commands = useMemo( () => { - const addNewPage = - isSiteEditor && isBlockBasedTheme - ? createPageEntity - : () => - ( document.location.href = - 'post-new.php?post_type=page' ); - return [ - { - name: 'core/add-new-page', - label: __( 'Add new page' ), - icon: plus, - callback: addNewPage, }, - ]; - }, [ createPageEntity, isSiteEditor, isBlockBasedTheme ] ); + [ createErrorNotice, history, saveEntityRecord ] + ); + + const commands = useMemo( () => { + const addNewPage = + isSiteEditor && isBlockBasedTheme + ? createPageEntity + : () => + ( document.location.href = + 'post-new.php?post_type=page' ); + return [ + { + name: 'core/add-new-page', + label: __( 'Add new page' ), + icon: plus, + callback: addNewPage, + }, + ]; + }, [ createPageEntity, isSiteEditor, isBlockBasedTheme ] ); - return { - isLoading: false, - commands, + return { + isLoading: false, + commands, + }; }; -} export function useAdminNavigationCommands() { useCommand( { @@ -100,6 +103,6 @@ export function useAdminNavigationCommands() { useCommandLoader( { name: 'core/add-new-page', - hook: useAddNewPageCommand, + hook: getAddNewPageCommand(), } ); } diff --git a/packages/core-commands/src/site-editor-navigation-commands.js b/packages/core-commands/src/site-editor-navigation-commands.js index 4679d4d1523c89..2785d809d41e03 100644 --- a/packages/core-commands/src/site-editor-navigation-commands.js +++ b/packages/core-commands/src/site-editor-navigation-commands.js @@ -275,159 +275,169 @@ const getNavigationCommandLoaderPerTemplate = ( templateType ) => }; }; -const usePageNavigationCommandLoader = - getNavigationCommandLoaderPerPostType( 'page' ); -const usePostNavigationCommandLoader = - getNavigationCommandLoaderPerPostType( 'post' ); -const useTemplateNavigationCommandLoader = - getNavigationCommandLoaderPerTemplate( 'wp_template' ); -const useTemplatePartNavigationCommandLoader = - getNavigationCommandLoaderPerTemplate( 'wp_template_part' ); +const getSiteEditorBasicNavigationCommands = () => + function useSiteEditorBasicNavigationCommands() { + const history = useHistory(); + const isSiteEditor = getPath( window.location.href )?.includes( + 'site-editor.php' + ); + const { isBlockBasedTheme, canCreateTemplate } = useSelect( + ( select ) => { + return { + isBlockBasedTheme: + select( coreStore ).getCurrentTheme()?.is_block_theme, + canCreateTemplate: select( coreStore ).canUser( 'create', { + kind: 'postType', + name: 'wp_template', + } ), + }; + }, + [] + ); + const commands = useMemo( () => { + const result = []; -function useSiteEditorBasicNavigationCommands() { - const history = useHistory(); - const isSiteEditor = getPath( window.location.href )?.includes( - 'site-editor.php' - ); - const { isBlockBasedTheme, canCreateTemplate } = useSelect( ( select ) => { - return { - isBlockBasedTheme: - select( coreStore ).getCurrentTheme()?.is_block_theme, - canCreateTemplate: select( coreStore ).canUser( 'create', { - kind: 'postType', - name: 'wp_template', - } ), - }; - }, [] ); - const commands = useMemo( () => { - const result = []; + if ( canCreateTemplate && isBlockBasedTheme ) { + result.push( { + name: 'core/edit-site/open-navigation', + label: __( 'Navigation' ), + icon: navigation, + callback: ( { close } ) => { + const args = { + postType: 'wp_navigation', + }; + const targetUrl = addQueryArgs( + 'site-editor.php', + args + ); + if ( isSiteEditor ) { + history.push( args ); + } else { + document.location = targetUrl; + } + close(); + }, + } ); - if ( canCreateTemplate && isBlockBasedTheme ) { - result.push( { - name: 'core/edit-site/open-navigation', - label: __( 'Navigation' ), - icon: navigation, - callback: ( { close } ) => { - const args = { - postType: 'wp_navigation', - }; - const targetUrl = addQueryArgs( 'site-editor.php', args ); - if ( isSiteEditor ) { - history.push( args ); - } else { - document.location = targetUrl; - } - close(); - }, - } ); + result.push( { + name: 'core/edit-site/open-styles', + label: __( 'Styles' ), + icon: styles, + callback: ( { close } ) => { + const args = { + path: '/wp_global_styles', + }; + const targetUrl = addQueryArgs( + 'site-editor.php', + args + ); + if ( isSiteEditor ) { + history.push( args ); + } else { + document.location = targetUrl; + } + close(); + }, + } ); - result.push( { - name: 'core/edit-site/open-styles', - label: __( 'Styles' ), - icon: styles, - callback: ( { close } ) => { - const args = { - path: '/wp_global_styles', - }; - const targetUrl = addQueryArgs( 'site-editor.php', args ); - if ( isSiteEditor ) { - history.push( args ); - } else { - document.location = targetUrl; - } - close(); - }, - } ); + result.push( { + name: 'core/edit-site/open-pages', + label: __( 'Pages' ), + icon: page, + callback: ( { close } ) => { + const args = { + postType: 'page', + }; + const targetUrl = addQueryArgs( + 'site-editor.php', + args + ); + if ( isSiteEditor ) { + history.push( args ); + } else { + document.location = targetUrl; + } + close(); + }, + } ); - result.push( { - name: 'core/edit-site/open-pages', - label: __( 'Pages' ), - icon: page, - callback: ( { close } ) => { - const args = { - postType: 'page', - }; - const targetUrl = addQueryArgs( 'site-editor.php', args ); - if ( isSiteEditor ) { - history.push( args ); - } else { - document.location = targetUrl; - } - close(); - }, - } ); + result.push( { + name: 'core/edit-site/open-templates', + label: __( 'Templates' ), + icon: layout, + callback: ( { close } ) => { + const args = { + postType: 'wp_template', + }; + const targetUrl = addQueryArgs( + 'site-editor.php', + args + ); + if ( isSiteEditor ) { + history.push( args ); + } else { + document.location = targetUrl; + } + close(); + }, + } ); + } result.push( { - name: 'core/edit-site/open-templates', - label: __( 'Templates' ), - icon: layout, + name: 'core/edit-site/open-patterns', + label: __( 'Patterns' ), + icon: symbol, callback: ( { close } ) => { - const args = { - postType: 'wp_template', - }; - const targetUrl = addQueryArgs( 'site-editor.php', args ); - if ( isSiteEditor ) { - history.push( args ); + if ( canCreateTemplate ) { + const args = { + postType: 'wp_block', + }; + const targetUrl = addQueryArgs( + 'site-editor.php', + args + ); + if ( isSiteEditor ) { + history.push( args ); + } else { + document.location = targetUrl; + } + close(); } else { - document.location = targetUrl; + // If a user cannot access the site editor + document.location.href = 'edit.php?post_type=wp_block'; } - close(); }, } ); - } - - result.push( { - name: 'core/edit-site/open-patterns', - label: __( 'Patterns' ), - icon: symbol, - callback: ( { close } ) => { - if ( canCreateTemplate ) { - const args = { - postType: 'wp_block', - }; - const targetUrl = addQueryArgs( 'site-editor.php', args ); - if ( isSiteEditor ) { - history.push( args ); - } else { - document.location = targetUrl; - } - close(); - } else { - // If a user cannot access the site editor - document.location.href = 'edit.php?post_type=wp_block'; - } - }, - } ); - return result; - }, [ history, isSiteEditor, canCreateTemplate, isBlockBasedTheme ] ); + return result; + }, [ history, isSiteEditor, canCreateTemplate, isBlockBasedTheme ] ); - return { - commands, - isLoading: false, + return { + commands, + isLoading: false, + }; }; -} export function useSiteEditorNavigationCommands() { useCommandLoader( { name: 'core/edit-site/navigate-pages', - hook: usePageNavigationCommandLoader, + hook: getNavigationCommandLoaderPerPostType( 'page' ), } ); useCommandLoader( { name: 'core/edit-site/navigate-posts', - hook: usePostNavigationCommandLoader, + hook: getNavigationCommandLoaderPerPostType( 'post' ), } ); useCommandLoader( { name: 'core/edit-site/navigate-templates', - hook: useTemplateNavigationCommandLoader, + hook: getNavigationCommandLoaderPerTemplate( 'wp_template' ), } ); useCommandLoader( { name: 'core/edit-site/navigate-template-parts', - hook: useTemplatePartNavigationCommandLoader, + hook: getNavigationCommandLoaderPerTemplate( 'wp_template_part' ), } ); useCommandLoader( { name: 'core/edit-site/basic-navigation', - hook: useSiteEditorBasicNavigationCommands, + hook: getSiteEditorBasicNavigationCommands(), context: 'site-editor', } ); } diff --git a/packages/edit-site/src/hooks/commands/use-common-commands.js b/packages/edit-site/src/hooks/commands/use-common-commands.js index 536817e88d3a47..3e87f8721e116a 100644 --- a/packages/edit-site/src/hooks/commands/use-common-commands.js +++ b/packages/edit-site/src/hooks/commands/use-common-commands.js @@ -28,282 +28,289 @@ import { store as editSiteStore } from '../../store'; const { useGlobalStylesReset } = unlock( blockEditorPrivateApis ); const { useHistory, useLocation } = unlock( routerPrivateApis ); -function useGlobalStylesOpenStylesCommands() { - const { openGeneralSidebar } = unlock( useDispatch( editSiteStore ) ); - const { params } = useLocation(); - const { canvas = 'view' } = params; - const history = useHistory(); - const isBlockBasedTheme = useSelect( ( select ) => { - return select( coreStore ).getCurrentTheme().is_block_theme; - }, [] ); +const getGlobalStylesOpenStylesCommands = () => + function useGlobalStylesOpenStylesCommands() { + const { openGeneralSidebar } = unlock( useDispatch( editSiteStore ) ); + const { params } = useLocation(); + const { canvas = 'view' } = params; + const history = useHistory(); + const isBlockBasedTheme = useSelect( ( select ) => { + return select( coreStore ).getCurrentTheme().is_block_theme; + }, [] ); - const commands = useMemo( () => { - if ( ! isBlockBasedTheme ) { - return []; - } + const commands = useMemo( () => { + if ( ! isBlockBasedTheme ) { + return []; + } - return [ - { - name: 'core/edit-site/open-styles', - label: __( 'Open styles' ), - callback: ( { close } ) => { - close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', - } ); - } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { ...params, canvas: 'edit' }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - } - openGeneralSidebar( 'edit-site/global-styles' ); + return [ + { + name: 'core/edit-site/open-styles', + label: __( 'Open styles' ), + callback: ( { close } ) => { + close(); + if ( ! params.postId ) { + history.push( { + path: '/wp_global_styles', + canvas: 'edit', + } ); + } + if ( params.postId && canvas !== 'edit' ) { + history.push( + { ...params, canvas: 'edit' }, + undefined, + { + transition: 'canvas-mode-edit-transition', + } + ); + } + openGeneralSidebar( 'edit-site/global-styles' ); + }, + icon: styles, }, - icon: styles, - }, - ]; - }, [ history, openGeneralSidebar, params, canvas, isBlockBasedTheme ] ); + ]; + }, [ history, openGeneralSidebar, params, canvas, isBlockBasedTheme ] ); - return { - isLoading: false, - commands, + return { + isLoading: false, + commands, + }; }; -} -function useGlobalStylesToggleWelcomeGuideCommands() { - const { openGeneralSidebar } = unlock( useDispatch( editSiteStore ) ); - const { params } = useLocation(); - const { canvas = 'view' } = params; - const { set } = useDispatch( preferencesStore ); +const getGlobalStylesToggleWelcomeGuideCommands = () => + function useGlobalStylesToggleWelcomeGuideCommands() { + const { openGeneralSidebar } = unlock( useDispatch( editSiteStore ) ); + const { params } = useLocation(); + const { canvas = 'view' } = params; + const { set } = useDispatch( preferencesStore ); - const history = useHistory(); - const isBlockBasedTheme = useSelect( ( select ) => { - return select( coreStore ).getCurrentTheme().is_block_theme; - }, [] ); + const history = useHistory(); + const isBlockBasedTheme = useSelect( ( select ) => { + return select( coreStore ).getCurrentTheme().is_block_theme; + }, [] ); - const commands = useMemo( () => { - if ( ! isBlockBasedTheme ) { - return []; - } + const commands = useMemo( () => { + if ( ! isBlockBasedTheme ) { + return []; + } - return [ - { - name: 'core/edit-site/toggle-styles-welcome-guide', - label: __( 'Learn about styles' ), - callback: ( { close } ) => { - close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', - } ); - } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { - ...params, + return [ + { + name: 'core/edit-site/toggle-styles-welcome-guide', + label: __( 'Learn about styles' ), + callback: ( { close } ) => { + close(); + if ( ! params.postId ) { + history.push( { + path: '/wp_global_styles', canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - } - openGeneralSidebar( 'edit-site/global-styles' ); - set( 'core/edit-site', 'welcomeGuideStyles', true ); - // sometimes there's a focus loss that happens after some time - // that closes the modal, we need to force reopening it. - setTimeout( () => { + } ); + } + if ( params.postId && canvas !== 'edit' ) { + history.push( + { + ...params, + canvas: 'edit', + }, + undefined, + { + transition: 'canvas-mode-edit-transition', + } + ); + } + openGeneralSidebar( 'edit-site/global-styles' ); set( 'core/edit-site', 'welcomeGuideStyles', true ); - }, 500 ); + // sometimes there's a focus loss that happens after some time + // that closes the modal, we need to force reopening it. + setTimeout( () => { + set( 'core/edit-site', 'welcomeGuideStyles', true ); + }, 500 ); + }, + icon: help, }, - icon: help, - }, - ]; - }, [ - history, - openGeneralSidebar, - canvas, - isBlockBasedTheme, - set, - params, - ] ); + ]; + }, [ + history, + openGeneralSidebar, + canvas, + isBlockBasedTheme, + set, + params, + ] ); - return { - isLoading: false, - commands, + return { + isLoading: false, + commands, + }; }; -} -function useGlobalStylesResetCommands() { - const [ canReset, onReset ] = useGlobalStylesReset(); - const commands = useMemo( () => { - if ( ! canReset ) { - return []; - } +const getGlobalStylesResetCommands = () => + function useGlobalStylesResetCommands() { + const [ canReset, onReset ] = useGlobalStylesReset(); + const commands = useMemo( () => { + if ( ! canReset ) { + return []; + } - return [ - { - name: 'core/edit-site/reset-global-styles', - label: __( 'Reset styles' ), - icon: isRTL() ? rotateRight : rotateLeft, - callback: ( { close } ) => { - close(); - onReset(); + return [ + { + name: 'core/edit-site/reset-global-styles', + label: __( 'Reset styles' ), + icon: isRTL() ? rotateRight : rotateLeft, + callback: ( { close } ) => { + close(); + onReset(); + }, }, - }, - ]; - }, [ canReset, onReset ] ); + ]; + }, [ canReset, onReset ] ); - return { - isLoading: false, - commands, + return { + isLoading: false, + commands, + }; }; -} -function useGlobalStylesOpenCssCommands() { - const { openGeneralSidebar, setEditorCanvasContainerView } = unlock( - useDispatch( editSiteStore ) - ); - const { params } = useLocation(); - const { canvas = 'view' } = params; - const history = useHistory(); - const { canEditCSS } = useSelect( ( select ) => { - const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = - select( coreStore ); +const getGlobalStylesOpenCssCommands = () => + function useGlobalStylesOpenCssCommands() { + const { openGeneralSidebar, setEditorCanvasContainerView } = unlock( + useDispatch( editSiteStore ) + ); + const { params } = useLocation(); + const { canvas = 'view' } = params; + const history = useHistory(); + const { canEditCSS } = useSelect( ( select ) => { + const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = + select( coreStore ); - const globalStylesId = __experimentalGetCurrentGlobalStylesId(); - const globalStyles = globalStylesId - ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) - : undefined; + const globalStylesId = __experimentalGetCurrentGlobalStylesId(); + const globalStyles = globalStylesId + ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) + : undefined; - return { - canEditCSS: !! globalStyles?._links?.[ 'wp:action-edit-css' ], - }; - }, [] ); + return { + canEditCSS: !! globalStyles?._links?.[ 'wp:action-edit-css' ], + }; + }, [] ); - const commands = useMemo( () => { - if ( ! canEditCSS ) { - return []; - } + const commands = useMemo( () => { + if ( ! canEditCSS ) { + return []; + } - return [ - { - name: 'core/edit-site/open-styles-css', - label: __( 'Customize CSS' ), - icon: brush, - callback: ( { close } ) => { - close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', - } ); - } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { - ...params, + return [ + { + name: 'core/edit-site/open-styles-css', + label: __( 'Customize CSS' ), + icon: brush, + callback: ( { close } ) => { + close(); + if ( ! params.postId ) { + history.push( { + path: '/wp_global_styles', canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } - ); - } - openGeneralSidebar( 'edit-site/global-styles' ); - setEditorCanvasContainerView( 'global-styles-css' ); + } ); + } + if ( params.postId && canvas !== 'edit' ) { + history.push( + { + ...params, + canvas: 'edit', + }, + undefined, + { + transition: 'canvas-mode-edit-transition', + } + ); + } + openGeneralSidebar( 'edit-site/global-styles' ); + setEditorCanvasContainerView( 'global-styles-css' ); + }, }, - }, - ]; - }, [ - history, - openGeneralSidebar, - setEditorCanvasContainerView, - canEditCSS, - canvas, - params, - ] ); - return { - isLoading: false, - commands, + ]; + }, [ + history, + openGeneralSidebar, + setEditorCanvasContainerView, + canEditCSS, + canvas, + params, + ] ); + return { + isLoading: false, + commands, + }; }; -} -function useGlobalStylesOpenRevisionsCommands() { - const { openGeneralSidebar, setEditorCanvasContainerView } = unlock( - useDispatch( editSiteStore ) - ); - const { params } = useLocation(); - const { canvas = 'view' } = params; - const history = useHistory(); - const hasRevisions = useSelect( ( select ) => { - const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = - select( coreStore ); - const globalStylesId = __experimentalGetCurrentGlobalStylesId(); - const globalStyles = globalStylesId - ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) - : undefined; - return !! globalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.count; - }, [] ); +const getGlobalStylesOpenRevisionsCommands = () => + function useGlobalStylesOpenRevisionsCommands() { + const { openGeneralSidebar, setEditorCanvasContainerView } = unlock( + useDispatch( editSiteStore ) + ); + const { params } = useLocation(); + const { canvas = 'view' } = params; + const history = useHistory(); + const hasRevisions = useSelect( ( select ) => { + const { getEntityRecord, __experimentalGetCurrentGlobalStylesId } = + select( coreStore ); + const globalStylesId = __experimentalGetCurrentGlobalStylesId(); + const globalStyles = globalStylesId + ? getEntityRecord( 'root', 'globalStyles', globalStylesId ) + : undefined; + return !! globalStyles?._links?.[ 'version-history' ]?.[ 0 ]?.count; + }, [] ); - const commands = useMemo( () => { - if ( ! hasRevisions ) { - return []; - } + const commands = useMemo( () => { + if ( ! hasRevisions ) { + return []; + } - return [ - { - name: 'core/edit-site/open-global-styles-revisions', - label: __( 'Style revisions' ), - icon: backup, - callback: ( { close } ) => { - close(); - if ( ! params.postId ) { - history.push( { - path: '/wp_global_styles', - canvas: 'edit', - } ); - } - if ( params.postId && canvas !== 'edit' ) { - history.push( - { - ...params, + return [ + { + name: 'core/edit-site/open-global-styles-revisions', + label: __( 'Style revisions' ), + icon: backup, + callback: ( { close } ) => { + close(); + if ( ! params.postId ) { + history.push( { + path: '/wp_global_styles', canvas: 'edit', - }, - undefined, - { - transition: 'canvas-mode-edit-transition', - } + } ); + } + if ( params.postId && canvas !== 'edit' ) { + history.push( + { + ...params, + canvas: 'edit', + }, + undefined, + { + transition: 'canvas-mode-edit-transition', + } + ); + } + openGeneralSidebar( 'edit-site/global-styles' ); + setEditorCanvasContainerView( + 'global-styles-revisions' ); - } - openGeneralSidebar( 'edit-site/global-styles' ); - setEditorCanvasContainerView( 'global-styles-revisions' ); + }, }, - }, - ]; - }, [ - hasRevisions, - history, - openGeneralSidebar, - setEditorCanvasContainerView, - canvas, - params, - ] ); + ]; + }, [ + hasRevisions, + history, + openGeneralSidebar, + setEditorCanvasContainerView, + canvas, + params, + ] ); - return { - isLoading: false, - commands, + return { + isLoading: false, + commands, + }; }; -} export function useCommonCommands() { const homeUrl = useSelect( ( select ) => { @@ -324,26 +331,26 @@ export function useCommonCommands() { useCommandLoader( { name: 'core/edit-site/open-styles', - hook: useGlobalStylesOpenStylesCommands, + hook: getGlobalStylesOpenStylesCommands(), } ); useCommandLoader( { name: 'core/edit-site/toggle-styles-welcome-guide', - hook: useGlobalStylesToggleWelcomeGuideCommands, + hook: getGlobalStylesToggleWelcomeGuideCommands(), } ); useCommandLoader( { name: 'core/edit-site/reset-global-styles', - hook: useGlobalStylesResetCommands, + hook: getGlobalStylesResetCommands(), } ); useCommandLoader( { name: 'core/edit-site/open-styles-css', - hook: useGlobalStylesOpenCssCommands, + hook: getGlobalStylesOpenCssCommands(), } ); useCommandLoader( { name: 'core/edit-site/open-styles-revisions', - hook: useGlobalStylesOpenRevisionsCommands, + hook: getGlobalStylesOpenRevisionsCommands(), } ); } diff --git a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js index 97283044193892..da36f32e6c0d51 100644 --- a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js +++ b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js @@ -22,147 +22,152 @@ import { useLink } from '../../components/routes/link'; const { useHistory, useLocation } = unlock( routerPrivateApis ); -function usePageContentFocusCommands() { - const { record: template } = useEditedEntityRecord(); - const { params } = useLocation(); - const { canvas = 'view' } = params; - const { isPage, templateId, currentPostType } = useSelect( ( select ) => { - const { isPage: _isPage } = unlock( select( editSiteStore ) ); - const { getCurrentPostType, getCurrentTemplateId } = - select( editorStore ); - return { - isPage: _isPage(), - templateId: getCurrentTemplateId(), - currentPostType: getCurrentPostType(), - }; - }, [] ); - - const { onClick: editTemplate } = useLink( { - postType: 'wp_template', - postId: templateId, - } ); - - const { setRenderingMode } = useDispatch( editorStore ); - - if ( ! isPage || canvas !== 'edit' ) { - return { isLoading: false, commands: [] }; - } - - const commands = []; - - if ( currentPostType !== 'wp_template' ) { - commands.push( { - name: 'core/switch-to-template-focus', - label: sprintf( - /* translators: %s: template title */ - __( 'Edit template: %s' ), - decodeEntities( template.title ) - ), - icon: layout, - callback: ( { close } ) => { - editTemplate(); - close(); - }, - } ); - } else { - commands.push( { - name: 'core/switch-to-page-focus', - label: __( 'Back to page' ), - icon: page, - callback: ( { close } ) => { - setRenderingMode( 'template-locked' ); - close(); +const getPageContentFocusCommands = () => + function usePageContentFocusCommands() { + const { record: template } = useEditedEntityRecord(); + const { params } = useLocation(); + const { canvas = 'view' } = params; + const { isPage, templateId, currentPostType } = useSelect( + ( select ) => { + const { isPage: _isPage } = unlock( select( editSiteStore ) ); + const { getCurrentPostType, getCurrentTemplateId } = + select( editorStore ); + return { + isPage: _isPage(), + templateId: getCurrentTemplateId(), + currentPostType: getCurrentPostType(), + }; }, + [] + ); + + const { onClick: editTemplate } = useLink( { + postType: 'wp_template', + postId: templateId, } ); - } - return { isLoading: false, commands }; -} + const { setRenderingMode } = useDispatch( editorStore ); + + if ( ! isPage || canvas !== 'edit' ) { + return { isLoading: false, commands: [] }; + } + + const commands = []; + + if ( currentPostType !== 'wp_template' ) { + commands.push( { + name: 'core/switch-to-template-focus', + label: sprintf( + /* translators: %s: template title */ + __( 'Edit template: %s' ), + decodeEntities( template.title ) + ), + icon: layout, + callback: ( { close } ) => { + editTemplate(); + close(); + }, + } ); + } else { + commands.push( { + name: 'core/switch-to-page-focus', + label: __( 'Back to page' ), + icon: page, + callback: ( { close } ) => { + setRenderingMode( 'template-locked' ); + close(); + }, + } ); + } + + return { isLoading: false, commands }; + }; -function useManipulateDocumentCommands() { - const { isLoaded, record: template } = useEditedEntityRecord(); - const { removeTemplate, revertTemplate } = useDispatch( editSiteStore ); - const history = useHistory(); - const isEditingPage = useSelect( - ( select ) => - select( editSiteStore ).isPage() && - select( editorStore ).getCurrentPostType() !== 'wp_template', - [] - ); - - if ( ! isLoaded ) { - return { isLoading: true, commands: [] }; - } - - const commands = []; - - if ( isTemplateRevertable( template ) && ! isEditingPage ) { - const label = - template.type === TEMPLATE_POST_TYPE - ? sprintf( - /* translators: %s: template title */ - __( 'Reset template: %s' ), - decodeEntities( template.title ) - ) - : sprintf( - /* translators: %s: template part title */ - __( 'Reset template part: %s' ), - decodeEntities( template.title ) - ); - commands.push( { - name: 'core/reset-template', - label, - icon: isRTL() ? rotateRight : rotateLeft, - callback: ( { close } ) => { - revertTemplate( template ); - close(); - }, - } ); - } - - if ( isTemplateRemovable( template ) && ! isEditingPage ) { - const label = - template.type === TEMPLATE_POST_TYPE - ? sprintf( - /* translators: %s: template title */ - __( 'Delete template: %s' ), - decodeEntities( template.title ) - ) - : sprintf( - /* translators: %s: template part title */ - __( 'Delete template part: %s' ), - decodeEntities( template.title ) - ); - commands.push( { - name: 'core/remove-template', - label, - icon: trash, - callback: ( { close } ) => { - removeTemplate( template ); - // Navigate to the template list - history.push( { - postType: template.type, - } ); - close(); - }, - } ); - } +const getManipulateDocumentCommands = () => + function useManipulateDocumentCommands() { + const { isLoaded, record: template } = useEditedEntityRecord(); + const { removeTemplate, revertTemplate } = useDispatch( editSiteStore ); + const history = useHistory(); + const isEditingPage = useSelect( + ( select ) => + select( editSiteStore ).isPage() && + select( editorStore ).getCurrentPostType() !== 'wp_template', + [] + ); + + if ( ! isLoaded ) { + return { isLoading: true, commands: [] }; + } + + const commands = []; + + if ( isTemplateRevertable( template ) && ! isEditingPage ) { + const label = + template.type === TEMPLATE_POST_TYPE + ? sprintf( + /* translators: %s: template title */ + __( 'Reset template: %s' ), + decodeEntities( template.title ) + ) + : sprintf( + /* translators: %s: template part title */ + __( 'Reset template part: %s' ), + decodeEntities( template.title ) + ); + commands.push( { + name: 'core/reset-template', + label, + icon: isRTL() ? rotateRight : rotateLeft, + callback: ( { close } ) => { + revertTemplate( template ); + close(); + }, + } ); + } + + if ( isTemplateRemovable( template ) && ! isEditingPage ) { + const label = + template.type === TEMPLATE_POST_TYPE + ? sprintf( + /* translators: %s: template title */ + __( 'Delete template: %s' ), + decodeEntities( template.title ) + ) + : sprintf( + /* translators: %s: template part title */ + __( 'Delete template part: %s' ), + decodeEntities( template.title ) + ); + commands.push( { + name: 'core/remove-template', + label, + icon: trash, + callback: ( { close } ) => { + removeTemplate( template ); + // Navigate to the template list + history.push( { + postType: template.type, + } ); + close(); + }, + } ); + } - return { - isLoading: ! isLoaded, - commands, + return { + isLoading: ! isLoaded, + commands, + }; }; -} export function useEditModeCommands() { useCommandLoader( { name: 'core/edit-site/page-content-focus', - hook: usePageContentFocusCommands, + hook: getPageContentFocusCommands(), context: 'entity-edit', } ); useCommandLoader( { name: 'core/edit-site/manipulate-document', - hook: useManipulateDocumentCommands, + hook: getManipulateDocumentCommands(), } ); } diff --git a/packages/editor/src/components/commands/index.js b/packages/editor/src/components/commands/index.js index b4b9c05db256d7..16260bed3978fd 100644 --- a/packages/editor/src/components/commands/index.js +++ b/packages/editor/src/components/commands/index.js @@ -30,279 +30,287 @@ import { PATTERN_POST_TYPE } from '../../store/constants'; import { modalName as patternRenameModalName } from '../pattern-rename-modal'; import { modalName as patternDuplicateModalName } from '../pattern-duplicate-modal'; -function useEditorCommandLoader() { - const { - editorMode, - isListViewOpen, - showBlockBreadcrumbs, - isDistractionFree, - isFocusMode, - isPreviewMode, - isViewable, - isCodeEditingEnabled, - isRichEditingEnabled, - isPublishSidebarEnabled, - } = useSelect( ( select ) => { - const { get } = select( preferencesStore ); - const { isListViewOpened, getCurrentPostType, getEditorSettings } = - select( editorStore ); - const { getSettings } = select( blockEditorStore ); - const { getPostType } = select( coreStore ); +const getEditorCommandLoader = () => + function useEditorCommandLoader() { + const { + editorMode, + isListViewOpen, + showBlockBreadcrumbs, + isDistractionFree, + isFocusMode, + isPreviewMode, + isViewable, + isCodeEditingEnabled, + isRichEditingEnabled, + isPublishSidebarEnabled, + } = useSelect( ( select ) => { + const { get } = select( preferencesStore ); + const { isListViewOpened, getCurrentPostType, getEditorSettings } = + select( editorStore ); + const { getSettings } = select( blockEditorStore ); + const { getPostType } = select( coreStore ); - return { - editorMode: get( 'core', 'editorMode' ) ?? 'visual', - isListViewOpen: isListViewOpened(), - showBlockBreadcrumbs: get( 'core', 'showBlockBreadcrumbs' ), - isDistractionFree: get( 'core', 'distractionFree' ), - isFocusMode: get( 'core', 'focusMode' ), - isPreviewMode: getSettings().isPreviewMode, - isViewable: getPostType( getCurrentPostType() )?.viewable ?? false, - isCodeEditingEnabled: getEditorSettings().codeEditingEnabled, - isRichEditingEnabled: getEditorSettings().richEditingEnabled, - isPublishSidebarEnabled: - select( editorStore ).isPublishSidebarEnabled(), - }; - }, [] ); - const { getActiveComplementaryArea } = useSelect( interfaceStore ); - const { toggle } = useDispatch( preferencesStore ); - const { createInfoNotice } = useDispatch( noticesStore ); - const { - __unstableSaveForPreview, - setIsListViewOpened, - switchEditorMode, - toggleDistractionFree, - toggleSpotlightMode, - toggleTopToolbar, - } = useDispatch( editorStore ); - const { openModal, enableComplementaryArea, disableComplementaryArea } = - useDispatch( interfaceStore ); - const { getCurrentPostId } = useSelect( editorStore ); - const allowSwitchEditorMode = isCodeEditingEnabled && isRichEditingEnabled; + return { + editorMode: get( 'core', 'editorMode' ) ?? 'visual', + isListViewOpen: isListViewOpened(), + showBlockBreadcrumbs: get( 'core', 'showBlockBreadcrumbs' ), + isDistractionFree: get( 'core', 'distractionFree' ), + isFocusMode: get( 'core', 'focusMode' ), + isPreviewMode: getSettings().isPreviewMode, + isViewable: + getPostType( getCurrentPostType() )?.viewable ?? false, + isCodeEditingEnabled: getEditorSettings().codeEditingEnabled, + isRichEditingEnabled: getEditorSettings().richEditingEnabled, + isPublishSidebarEnabled: + select( editorStore ).isPublishSidebarEnabled(), + }; + }, [] ); + const { getActiveComplementaryArea } = useSelect( interfaceStore ); + const { toggle } = useDispatch( preferencesStore ); + const { createInfoNotice } = useDispatch( noticesStore ); + const { + __unstableSaveForPreview, + setIsListViewOpened, + switchEditorMode, + toggleDistractionFree, + toggleSpotlightMode, + toggleTopToolbar, + } = useDispatch( editorStore ); + const { openModal, enableComplementaryArea, disableComplementaryArea } = + useDispatch( interfaceStore ); + const { getCurrentPostId } = useSelect( editorStore ); + const allowSwitchEditorMode = + isCodeEditingEnabled && isRichEditingEnabled; - if ( isPreviewMode ) { - return { commands: [], isLoading: false }; - } + if ( isPreviewMode ) { + return { commands: [], isLoading: false }; + } - const commands = []; + const commands = []; - commands.push( { - name: 'core/open-shortcut-help', - label: __( 'Keyboard shortcuts' ), - icon: keyboard, - callback: ( { close } ) => { - close(); - openModal( 'editor/keyboard-shortcut-help' ); - }, - } ); - - commands.push( { - name: 'core/toggle-distraction-free', - label: isDistractionFree - ? __( 'Exit Distraction free' ) - : __( 'Enter Distraction free' ), - callback: ( { close } ) => { - toggleDistractionFree(); - close(); - }, - } ); - - commands.push( { - name: 'core/open-preferences', - label: __( 'Editor preferences' ), - callback: ( { close } ) => { - close(); - openModal( 'editor/preferences' ); - }, - } ); - - commands.push( { - name: 'core/toggle-spotlight-mode', - label: isFocusMode - ? __( 'Exit Spotlight mode' ) - : __( 'Enter Spotlight mode' ), - callback: ( { close } ) => { - toggleSpotlightMode(); - close(); - }, - } ); - - commands.push( { - name: 'core/toggle-list-view', - label: isListViewOpen - ? __( 'Close List View' ) - : __( 'Open List View' ), - icon: listView, - callback: ( { close } ) => { - setIsListViewOpened( ! isListViewOpen ); - close(); - createInfoNotice( - isListViewOpen ? __( 'List View off.' ) : __( 'List View on.' ), - { - id: 'core/editor/toggle-list-view/notice', - type: 'snackbar', - } - ); - }, - } ); + commands.push( { + name: 'core/open-shortcut-help', + label: __( 'Keyboard shortcuts' ), + icon: keyboard, + callback: ( { close } ) => { + close(); + openModal( 'editor/keyboard-shortcut-help' ); + }, + } ); - commands.push( { - name: 'core/toggle-top-toolbar', - label: __( 'Top toolbar' ), - callback: ( { close } ) => { - toggleTopToolbar(); - close(); - }, - } ); + commands.push( { + name: 'core/toggle-distraction-free', + label: isDistractionFree + ? __( 'Exit Distraction free' ) + : __( 'Enter Distraction free' ), + callback: ( { close } ) => { + toggleDistractionFree(); + close(); + }, + } ); - if ( allowSwitchEditorMode ) { commands.push( { - name: 'core/toggle-code-editor', - label: - editorMode === 'visual' - ? __( 'Open code editor' ) - : __( 'Exit code editor' ), - icon: code, + name: 'core/open-preferences', + label: __( 'Editor preferences' ), callback: ( { close } ) => { - switchEditorMode( editorMode === 'visual' ? 'text' : 'visual' ); close(); + openModal( 'editor/preferences' ); }, } ); - } - commands.push( { - name: 'core/toggle-breadcrumbs', - label: showBlockBreadcrumbs - ? __( 'Hide block breadcrumbs' ) - : __( 'Show block breadcrumbs' ), - callback: ( { close } ) => { - toggle( 'core', 'showBlockBreadcrumbs' ); - close(); - createInfoNotice( - showBlockBreadcrumbs - ? __( 'Breadcrumbs hidden.' ) - : __( 'Breadcrumbs visible.' ), - { - id: 'core/editor/toggle-breadcrumbs/notice', - type: 'snackbar', - } - ); - }, - } ); + commands.push( { + name: 'core/toggle-spotlight-mode', + label: isFocusMode + ? __( 'Exit Spotlight mode' ) + : __( 'Enter Spotlight mode' ), + callback: ( { close } ) => { + toggleSpotlightMode(); + close(); + }, + } ); - commands.push( { - name: 'core/open-settings-sidebar', - label: __( 'Show or hide the Settings panel.' ), - icon: isRTL() ? drawerLeft : drawerRight, - callback: ( { close } ) => { - const activeSidebar = getActiveComplementaryArea( 'core' ); - close(); - if ( activeSidebar === 'edit-post/document' ) { - disableComplementaryArea( 'core' ); - } else { - enableComplementaryArea( 'core', 'edit-post/document' ); - } - }, - } ); + commands.push( { + name: 'core/toggle-list-view', + label: isListViewOpen + ? __( 'Close List View' ) + : __( 'Open List View' ), + icon: listView, + callback: ( { close } ) => { + setIsListViewOpened( ! isListViewOpen ); + close(); + createInfoNotice( + isListViewOpen + ? __( 'List View off.' ) + : __( 'List View on.' ), + { + id: 'core/editor/toggle-list-view/notice', + type: 'snackbar', + } + ); + }, + } ); - commands.push( { - name: 'core/open-block-inspector', - label: __( 'Show or hide the Block settings panel' ), - icon: blockDefault, - callback: ( { close } ) => { - const activeSidebar = getActiveComplementaryArea( 'core' ); - close(); - if ( activeSidebar === 'edit-post/block' ) { - disableComplementaryArea( 'core' ); - } else { - enableComplementaryArea( 'core', 'edit-post/block' ); - } - }, - } ); + commands.push( { + name: 'core/toggle-top-toolbar', + label: __( 'Top toolbar' ), + callback: ( { close } ) => { + toggleTopToolbar(); + close(); + }, + } ); - commands.push( { - name: 'core/toggle-publish-sidebar', - label: isPublishSidebarEnabled - ? __( 'Disable pre-publish checks' ) - : __( 'Enable pre-publish checks' ), - icon: formatListBullets, - callback: ( { close } ) => { - close(); - toggle( 'core', 'isPublishSidebarEnabled' ); - createInfoNotice( - isPublishSidebarEnabled - ? __( 'Pre-publish checks disabled.' ) - : __( 'Pre-publish checks enabled.' ), - { - id: 'core/editor/publish-sidebar/notice', - type: 'snackbar', - } - ); - }, - } ); + if ( allowSwitchEditorMode ) { + commands.push( { + name: 'core/toggle-code-editor', + label: + editorMode === 'visual' + ? __( 'Open code editor' ) + : __( 'Exit code editor' ), + icon: code, + callback: ( { close } ) => { + switchEditorMode( + editorMode === 'visual' ? 'text' : 'visual' + ); + close(); + }, + } ); + } - if ( isViewable ) { commands.push( { - name: 'core/preview-link', - label: __( 'Preview in a new tab' ), - icon: external, - callback: async ( { close } ) => { + name: 'core/toggle-breadcrumbs', + label: showBlockBreadcrumbs + ? __( 'Hide block breadcrumbs' ) + : __( 'Show block breadcrumbs' ), + callback: ( { close } ) => { + toggle( 'core', 'showBlockBreadcrumbs' ); close(); - const postId = getCurrentPostId(); - const link = await __unstableSaveForPreview(); - window.open( link, `wp-preview-${ postId }` ); + createInfoNotice( + showBlockBreadcrumbs + ? __( 'Breadcrumbs hidden.' ) + : __( 'Breadcrumbs visible.' ), + { + id: 'core/editor/toggle-breadcrumbs/notice', + type: 'snackbar', + } + ); }, } ); - } - - return { - commands, - isLoading: false, - }; -} -function useEditedEntityContextualCommands() { - const { postType } = useSelect( ( select ) => { - const { getCurrentPostType } = select( editorStore ); - return { - postType: getCurrentPostType(), - }; - }, [] ); - const { openModal } = useDispatch( interfaceStore ); - const commands = []; + commands.push( { + name: 'core/open-settings-sidebar', + label: __( 'Show or hide the Settings panel.' ), + icon: isRTL() ? drawerLeft : drawerRight, + callback: ( { close } ) => { + const activeSidebar = getActiveComplementaryArea( 'core' ); + close(); + if ( activeSidebar === 'edit-post/document' ) { + disableComplementaryArea( 'core' ); + } else { + enableComplementaryArea( 'core', 'edit-post/document' ); + } + }, + } ); - if ( postType === PATTERN_POST_TYPE ) { commands.push( { - name: 'core/rename-pattern', - label: __( 'Rename pattern' ), - icon: edit, + name: 'core/open-block-inspector', + label: __( 'Show or hide the Block settings panel' ), + icon: blockDefault, callback: ( { close } ) => { - openModal( patternRenameModalName ); + const activeSidebar = getActiveComplementaryArea( 'core' ); close(); + if ( activeSidebar === 'edit-post/block' ) { + disableComplementaryArea( 'core' ); + } else { + enableComplementaryArea( 'core', 'edit-post/block' ); + } }, } ); + commands.push( { - name: 'core/duplicate-pattern', - label: __( 'Duplicate pattern' ), - icon: symbol, + name: 'core/toggle-publish-sidebar', + label: isPublishSidebarEnabled + ? __( 'Disable pre-publish checks' ) + : __( 'Enable pre-publish checks' ), + icon: formatListBullets, callback: ( { close } ) => { - openModal( patternDuplicateModalName ); close(); + toggle( 'core', 'isPublishSidebarEnabled' ); + createInfoNotice( + isPublishSidebarEnabled + ? __( 'Pre-publish checks disabled.' ) + : __( 'Pre-publish checks enabled.' ), + { + id: 'core/editor/publish-sidebar/notice', + type: 'snackbar', + } + ); }, } ); - } - return { isLoading: false, commands }; -} + if ( isViewable ) { + commands.push( { + name: 'core/preview-link', + label: __( 'Preview in a new tab' ), + icon: external, + callback: async ( { close } ) => { + close(); + const postId = getCurrentPostId(); + const link = await __unstableSaveForPreview(); + window.open( link, `wp-preview-${ postId }` ); + }, + } ); + } + + return { + commands, + isLoading: false, + }; + }; + +const getEditedEntityContextualCommands = () => + function useEditedEntityContextualCommands() { + const { postType } = useSelect( ( select ) => { + const { getCurrentPostType } = select( editorStore ); + return { + postType: getCurrentPostType(), + }; + }, [] ); + const { openModal } = useDispatch( interfaceStore ); + const commands = []; + + if ( postType === PATTERN_POST_TYPE ) { + commands.push( { + name: 'core/rename-pattern', + label: __( 'Rename pattern' ), + icon: edit, + callback: ( { close } ) => { + openModal( patternRenameModalName ); + close(); + }, + } ); + commands.push( { + name: 'core/duplicate-pattern', + label: __( 'Duplicate pattern' ), + icon: symbol, + callback: ( { close } ) => { + openModal( patternDuplicateModalName ); + close(); + }, + } ); + } + + return { isLoading: false, commands }; + }; export default function useCommands() { useCommandLoader( { name: 'core/editor/edit-ui', - hook: useEditorCommandLoader, + hook: getEditorCommandLoader(), } ); useCommandLoader( { name: 'core/editor/contextual-commands', - hook: useEditedEntityContextualCommands, + hook: getEditedEntityContextualCommands(), context: 'entity-edit', } ); } From f15b4c19175da72843234cc6c862296ce9a4dcf8 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Thu, 7 Nov 2024 09:55:42 +0100 Subject: [PATCH 041/605] Fix inconsistent sidebars close buttons sizes (#66756) * Increase complementary area and tabbed sidebar X close button size. * Adjust widgets header actions right padding and gap. * Adjust edit widgets list view X close button size. Co-authored-by: afercia Co-authored-by: mikachan Co-authored-by: richtabor Co-authored-by: t-hamano Co-authored-by: jameskoster --- .../block-editor/src/components/tabbed-sidebar/index.js | 2 +- .../block-editor/src/components/tabbed-sidebar/style.scss | 2 +- packages/edit-widgets/src/components/header/style.scss | 7 ++++--- .../src/components/secondary-sidebar/list-view-sidebar.js | 2 +- .../src/components/secondary-sidebar/style.scss | 2 +- packages/editor/src/components/sidebar/style.scss | 4 +--- .../src/components/complementary-area-header/style.scss | 4 ++-- .../interface/src/components/complementary-area/index.js | 2 +- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/tabbed-sidebar/index.js b/packages/block-editor/src/components/tabbed-sidebar/index.js index a0cb510c720904..c9ff6bbf6555f0 100644 --- a/packages/block-editor/src/components/tabbed-sidebar/index.js +++ b/packages/block-editor/src/components/tabbed-sidebar/index.js @@ -33,7 +33,7 @@ function TabbedSidebar( icon={ closeSmall } label={ closeButtonLabel } onClick={ () => onClose() } - size="small" + size="compact" /> { __( 'List View' ) }
disableComplementaryArea( scope ) } toggleButtonProps={ { label: closeLabel, - size: 'small', + size: 'compact', shortcut: toggleShortcut, scope, identifier, From b0d2041e2b1de19aed1f088b4f9b6068cfd89e47 Mon Sep 17 00:00:00 2001 From: Ella <4710635+ellatrix@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:06:59 +0100 Subject: [PATCH 042/605] Iframe: always enable for block themes, in core too (#66800) Co-authored-by: ellatrix Co-authored-by: jsnajdr --- .../components/layout/use-should-iframe.js | 50 +++++++------------ 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/packages/edit-post/src/components/layout/use-should-iframe.js b/packages/edit-post/src/components/layout/use-should-iframe.js index 6da344c138f64b..cd2a893d8d1cd0 100644 --- a/packages/edit-post/src/components/layout/use-should-iframe.js +++ b/packages/edit-post/src/components/layout/use-should-iframe.js @@ -11,39 +11,27 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; */ import { unlock } from '../../lock-unlock'; -const isGutenbergPlugin = globalThis.IS_GUTENBERG_PLUGIN ? true : false; - export function useShouldIframe() { - const { - isBlockBasedTheme, - hasV3BlocksOnly, - isEditingTemplateOrPattern, - isZoomOutMode, - deviceType, - } = useSelect( ( select ) => { + return useSelect( ( select ) => { const { getEditorSettings, getCurrentPostType, getDeviceType } = select( editorStore ); - const { isZoomOut } = unlock( select( blockEditorStore ) ); - const { getBlockTypes } = select( blocksStore ); - const editorSettings = getEditorSettings(); - return { - isBlockBasedTheme: editorSettings.__unstableIsBlockBasedTheme, - hasV3BlocksOnly: getBlockTypes().every( ( type ) => { - return type.apiVersion >= 3; - } ), - isEditingTemplateOrPattern: [ 'wp_template', 'wp_block' ].includes( - getCurrentPostType() - ), - isZoomOutMode: isZoomOut(), - deviceType: getDeviceType(), - }; + return ( + // If the theme is block based, we ALWAYS use the iframe for + // consistency across the post and site editor. The iframe was + // introduced long before the sited editor and block themes, so + // these themes are expecting it. + getEditorSettings().__unstableIsBlockBasedTheme || + // For classic themes, we also still want to iframe all the special + // editor features and modes such as device previews, zoom out, and + // template/pattern editing. + getDeviceType() !== 'Desktop' || + [ 'wp_template', 'wp_block' ].includes( getCurrentPostType() ) || + unlock( select( blockEditorStore ) ).isZoomOut() || + // Finally, still iframe the editor for classic themes if all blocks + // are v3 (which means they are marked as iframe-compatible). + select( blocksStore ) + .getBlockTypes() + .every( ( type ) => type.apiVersion >= 3 ) + ); }, [] ); - - return ( - hasV3BlocksOnly || - ( isGutenbergPlugin && isBlockBasedTheme ) || - isEditingTemplateOrPattern || - isZoomOutMode || - [ 'Tablet', 'Mobile' ].includes( deviceType ) - ); } From 69f71e1170ced7897d5287ea3fbc9627e4b63c7f Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Thu, 7 Nov 2024 18:14:20 +0900 Subject: [PATCH 043/605] Data: Rename useSelect internals to fix React Compiler violations (#66807) Co-authored-by: tyxla Co-authored-by: Mamaduka --- packages/data/src/components/use-select/index.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index 3e28e9489e977e..15a3c88d2d5543 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -209,11 +209,11 @@ function Store( registry, suspense ) { }; } -function useStaticSelect( storeName ) { +function _useStaticSelect( storeName ) { return useRegistry().select( storeName ); } -function useMappingSelect( suspense, mapSelect, deps ) { +function _useMappingSelect( suspense, mapSelect, deps ) { const registry = useRegistry(); const isAsync = useAsyncMode(); const store = useMemo( @@ -308,13 +308,11 @@ export default function useSelect( mapSelect, deps ) { ); } - /* eslint-disable react-hooks/rules-of-hooks */ // `staticSelectMode` is not allowed to change during the hook instance's, // lifetime, so the rules of hooks are not really violated. return staticSelectMode - ? useStaticSelect( mapSelect ) - : useMappingSelect( false, mapSelect, deps ); - /* eslint-enable react-hooks/rules-of-hooks */ + ? _useStaticSelect( mapSelect ) + : _useMappingSelect( false, mapSelect, deps ); } /** @@ -337,5 +335,5 @@ export default function useSelect( mapSelect, deps ) { * @return {ReturnType} Data object returned by the `mapSelect` function. */ export function useSuspenseSelect( mapSelect, deps ) { - return useMappingSelect( true, mapSelect, deps ); + return _useMappingSelect( true, mapSelect, deps ); } From 7874fc5526618666f081b131b9cf0c6489c6cd9c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 7 Nov 2024 10:46:07 +0100 Subject: [PATCH 044/605] Block Editor: Fix React Compiler error for 'BlockProps' util (#66809) Co-authored-by: Mamaduka Co-authored-by: tyxla --- packages/block-editor/src/hooks/utils.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index fa710fa7f08eba..4334f70b9d13bf 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -562,8 +562,13 @@ export function createBlockEditFilter( features ) { addFilter( 'editor.BlockEdit', 'core/editor/hooks', withBlockEditHooks ); } -function BlockProps( { index, useBlockProps, setAllWrapperProps, ...props } ) { - const wrapperProps = useBlockProps( props ); +function BlockProps( { + index, + useBlockProps: hook, + setAllWrapperProps, + ...props +} ) { + const wrapperProps = hook( props ); const setWrapperProps = ( next ) => setAllWrapperProps( ( prev ) => { const nextAll = [ ...prev ]; From cb24e93252cf8aaa64d3ea4db337c950c1c74aec Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:03:34 +0900 Subject: [PATCH 045/605] Enable zoom out mode for non-iframe editor (#66789) Co-authored-by: t-hamano Co-authored-by: stokesman Co-authored-by: annezazu Co-authored-by: ndiego Co-authored-by: ellatrix --- packages/editor/src/components/editor-interface/index.js | 1 - packages/editor/src/components/header/index.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/editor/src/components/editor-interface/index.js b/packages/editor/src/components/editor-interface/index.js index aee75695b3fda4..3692a5ed797f5e 100644 --- a/packages/editor/src/components/editor-interface/index.js +++ b/packages/editor/src/components/editor-interface/index.js @@ -131,7 +131,6 @@ export default function EditorInterface( { customSaveButton={ customSaveButton } forceDisableBlockTools={ forceDisableBlockTools } title={ title } - isEditorIframed={ ! disableIframe } /> ) } diff --git a/packages/editor/src/components/header/index.js b/packages/editor/src/components/header/index.js index 5a9cc2f94a5951..2a5629b080caf8 100644 --- a/packages/editor/src/components/header/index.js +++ b/packages/editor/src/components/header/index.js @@ -53,7 +53,6 @@ function Header( { forceDisableBlockTools, setEntitiesSavedStatesCallback, title, - isEditorIframed, } ) { const isWideViewport = useViewportMatch( 'large' ); const isLargeViewport = useViewportMatch( 'medium' ); @@ -168,7 +167,7 @@ function Header( { forceIsAutosaveable={ forceIsDirty } /> - { canBeZoomedOut && isEditorIframed && isWideViewport && ( + { canBeZoomedOut && isWideViewport && ( ) } From 9671f7c34a4e72cbba6a2bdfd375c6c29987cbd7 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 8 Nov 2024 00:57:28 +0900 Subject: [PATCH 046/605] Core Commands: Fix add new post URL assignment (#66830) Co-authored-by: tyxla Co-authored-by: Mamaduka --- packages/core-commands/src/admin-navigation-commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-commands/src/admin-navigation-commands.js b/packages/core-commands/src/admin-navigation-commands.js index 9f0883faa3c17a..8a8167bb29b820 100644 --- a/packages/core-commands/src/admin-navigation-commands.js +++ b/packages/core-commands/src/admin-navigation-commands.js @@ -97,7 +97,7 @@ export function useAdminNavigationCommands() { label: __( 'Add new post' ), icon: plus, callback: () => { - document.location.href = 'post-new.php'; + document.location.assign( 'post-new.php' ); }, } ); From ab6e729daf775ee6065a48c210826af8281a0842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20W=C3=BCnsch?= Date: Thu, 7 Nov 2024 17:32:51 +0100 Subject: [PATCH 047/605] Iframe: Fix relative wp-content URLs (#66751) Unlinked contributors: tambourine-man, Pat-Relentless, nicolasleroy. Co-authored-by: Soean Co-authored-by: youknowriad Co-authored-by: ellatrix Co-authored-by: getdave Co-authored-by: cweiske Co-authored-by: peterwilsoncc --- .../block-editor/src/components/iframe/index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 85e1f12a7c0d63..76d2e09dfb7a30 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -191,6 +191,22 @@ function Iframe( { preventFileDropDefault, false ); + // Prevent clicks on links from navigating away. Note that links + // inside `contenteditable` are already disabled by the browser, so + // this is for links in blocks outside of `contenteditable`. + iFrameDocument.addEventListener( 'click', ( event ) => { + if ( event.target.tagName === 'A' ) { + event.preventDefault(); + + // Appending a hash to the current URL will not reload the + // page. This is useful for e.g. footnotes. + const href = event.target.getAttribute( 'href' ); + if ( href.startsWith( '#' ) ) { + iFrameDocument.defaultView.location.hash = + href.slice( 1 ); + } + } + } ); } node.addEventListener( 'load', onLoad ); @@ -272,6 +288,7 @@ function Iframe( { +
' ); - * true === $processor->next_tag( 'DIV' ); - * - * #### Special elements - * - * Some HTML elements are handled in a special way; their start and end tags - * act like a void tag. These are special because their contents can't contain - * HTML markup. Everything inside these elements is handled in a special way - * and content that _appears_ like HTML tags inside of them isn't. There can - * be no nesting in these elements. - * - * In the following list, "raw text" means that all of the content in the HTML - * until the matching closing tag is treated verbatim without any replacements - * and without any parsing. - * - * - IFRAME allows no content but requires a closing tag. - * - NOEMBED (deprecated) content is raw text. - * - NOFRAMES (deprecated) content is raw text. - * - SCRIPT content is plaintext apart from legacy rules allowing `` inside an HTML comment. - * - STYLE content is raw text. - * - TITLE content is plain text but character references are decoded. - * - TEXTAREA content is plain text but character references are decoded. - * - XMP (deprecated) content is raw text. - * - * ### Modifying HTML attributes for a found tag - * - * Once you've found the start of an opening tag you can modify - * any number of the attributes on that tag. You can set a new - * value for an attribute, remove the entire attribute, or do - * nothing and move on to the next opening tag. - * - * Example: - * - * if ( $tags->next_tag( array( 'class_name' => 'wp-group-block' ) ) ) { - * $tags->set_attribute( 'title', 'This groups the contained content.' ); - * $tags->remove_attribute( 'data-test-id' ); - * } - * - * If `set_attribute()` is called for an existing attribute it will - * overwrite the existing value. Similarly, calling `remove_attribute()` - * for a non-existing attribute has no effect on the document. Both - * of these methods are safe to call without knowing if a given attribute - * exists beforehand. - * - * ### Modifying CSS classes for a found tag - * - * The tag processor treats the `class` attribute as a special case. - * Because it's a common operation to add or remove CSS classes, this - * interface adds helper methods to make that easier. - * - * As with attribute values, adding or removing CSS classes is a safe - * operation that doesn't require checking if the attribute or class - * exists before making changes. If removing the only class then the - * entire `class` attribute will be removed. - * - * Example: - * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); - * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); - * - * // from `Yippee!` - * // to `Yippee!` - * $tags->add_class( 'is-active' ); - * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * - * // from `` - * // to ` - * $tags->remove_class( 'rugby' ); - * - * When class changes are enqueued but a direct change to `class` is made via - * `set_attribute` then the changes to `set_attribute` (or `remove_attribute`) - * will take precedence over those made through `add_class` and `remove_class`. - * - * ### Bookmarks - * - * While scanning through the input HTMl document it's possible to set - * a named bookmark when a particular tag is found. Later on, after - * continuing to scan other tags, it's possible to `seek` to one of - * the set bookmarks and then proceed again from that point forward. - * - * Because bookmarks create processing overhead one should avoid - * creating too many of them. As a rule, create only bookmarks - * of known string literal names; avoid creating "mark_{$index}" - * and so on. It's fine from a performance standpoint to create a - * bookmark and update it frequently, such as within a loop. - * - * $total_todos = 0; - * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) { - * $p->set_bookmark( 'list-start' ); - * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) { - * $p->set_bookmark( 'list-end' ); - * $p->seek( 'list-start' ); - * $p->set_attribute( 'data-contained-todos', (string) $total_todos ); - * $total_todos = 0; - * $p->seek( 'list-end' ); - * break; - * } - * - * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) { - * $total_todos++; - * } - * } - * } - * - * ## Tokens and finer-grained processing. - * - * It's possible to scan through every lexical token in the - * HTML document using the `next_token()` function. This - * alternative form takes no argument and provides no built-in - * query syntax. - * - * Example: - * - * $title = '(untitled)'; - * $text = ''; - * while ( $processor->next_token() ) { - * switch ( $processor->get_token_name() ) { - * case '#text': - * $text .= $processor->get_modifiable_text(); - * break; - * - * case 'BR': - * $text .= "\n"; - * break; - * - * case 'TITLE': - * $title = $processor->get_modifiable_text(); - * break; - * } - * } - * return trim( "# {$title}\n\n{$text}" ); - * - * ### Tokens and _modifiable text_. - * - * #### Special "atomic" HTML elements. - * - * Not all HTML elements are able to contain other elements inside of them. - * For instance, the contents inside a TITLE element are plaintext (except - * that character references like & will be decoded). This means that - * if the string `` appears inside a TITLE element, then it's not an - * image tag, but rather it's text describing an image tag. Likewise, the - * contents of a SCRIPT or STYLE element are handled entirely separately in - * a browser than the contents of other elements because they represent a - * different language than HTML. - * - * For these elements the Tag Processor treats the entire sequence as one, - * from the opening tag, including its contents, through its closing tag. - * This means that the it's not possible to match the closing tag for a - * SCRIPT element unless it's unexpected; the Tag Processor already matched - * it when it found the opening tag. - * - * The inner contents of these elements are that element's _modifiable text_. - * - * The special elements are: - * - `SCRIPT` whose contents are treated as raw plaintext but supports a legacy - * style of including JavaScript inside of HTML comments to avoid accidentally - * closing the SCRIPT from inside a JavaScript string. E.g. `console.log( '' )`. - * - `TITLE` and `TEXTAREA` whose contents are treated as plaintext and then any - * character references are decoded. E.g. `1 < 2 < 3` becomes `1 < 2 < 3`. - * - `IFRAME`, `NOSCRIPT`, `NOEMBED`, `NOFRAME`, `STYLE` whose contents are treated as - * raw plaintext and left as-is. E.g. `1 < 2 < 3` remains `1 < 2 < 3`. - * - * #### Other tokens with modifiable text. - * - * There are also non-elements which are void/self-closing in nature and contain - * modifiable text that is part of that individual syntax token itself. - * - * - `#text` nodes, whose entire token _is_ the modifiable text. - * - HTML comments and tokens that become comments due to some syntax error. The - * text for these tokens is the portion of the comment inside of the syntax. - * E.g. for `` the text is `" comment "` (note the spaces are included). - * - `CDATA` sections, whose text is the content inside of the section itself. E.g. for - * `` the text is `"some content"` (with restrictions [1]). - * - "Funky comments," which are a special case of invalid closing tags whose name is - * invalid. The text for these nodes is the text that a browser would transform into - * an HTML comment when parsing. E.g. for `` the text is `%post_author`. - * - `DOCTYPE` declarations like `` which have no closing tag. - * - XML Processing instruction nodes like `` (with restrictions [2]). - * - The empty end tag `` which is ignored in the browser and DOM. - * - * [1]: There are no CDATA sections in HTML. When encountering `` becomes a bogus HTML comment, meaning there can be no CDATA - * section in an HTML document containing `>`. The Tag Processor will first find - * all valid and bogus HTML comments, and then if the comment _would_ have been a - * CDATA section _were they to exist_, it will indicate this as the type of comment. - * - * [2]: XML allows a broader range of characters in a processing instruction's target name - * and disallows "xml" as a name, since it's special. The Tag Processor only recognizes - * target names with an ASCII-representable subset of characters. It also exhibits the - * same constraint as with CDATA sections, in that `>` cannot exist within the token - * since Processing Instructions do no exist within HTML and their syntax transforms - * into a bogus comment in the DOM. - * - * ## Design and limitations - * - * The Tag Processor is designed to linearly scan HTML documents and tokenize - * HTML tags and their attributes. It's designed to do this as efficiently as - * possible without compromising parsing integrity. Therefore it will be - * slower than some methods of modifying HTML, such as those incorporating - * over-simplified PCRE patterns, but will not introduce the defects and - * failures that those methods bring in, which lead to broken page renders - * and often to security vulnerabilities. On the other hand, it will be faster - * than full-blown HTML parsers such as DOMDocument and use considerably - * less memory. It requires a negligible memory overhead, enough to consider - * it a zero-overhead system. - * - * The performance characteristics are maintained by avoiding tree construction - * and semantic cleanups which are specified in HTML5. Because of this, for - * example, it's not possible for the Tag Processor to associate any given - * opening tag with its corresponding closing tag, or to return the inner markup - * inside an element. Systems may be built on top of the Tag Processor to do - * this, but the Tag Processor is and should be constrained so it can remain an - * efficient, low-level, and reliable HTML scanner. - * - * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy. - * HTML5 specifies that certain invalid content be transformed into different forms - * for display, such as removing null bytes from an input document and replacing - * invalid characters with the Unicode replacement character `U+FFFD` (visually "�"). - * Where errors or transformations exist within the HTML5 specification, the Tag Processor - * leaves those invalid inputs untouched, passing them through to the final browser - * to handle. While this implies that certain operations will be non-spec-compliant, - * such as reading the value of an attribute with invalid content, it also preserves a - * simplicity and efficiency for handling those error cases. - * - * Most operations within the Tag Processor are designed to minimize the difference - * between an input and output document for any given change. For example, the - * `add_class` and `remove_class` methods preserve whitespace and the class ordering - * within the `class` attribute; and when encountering tags with duplicated attributes, - * the Tag Processor will leave those invalid duplicate attributes where they are but - * update the proper attribute which the browser will read for parsing its value. An - * exception to this rule is that all attribute updates store their values as - * double-quoted strings, meaning that attributes on input with single-quoted or - * unquoted values will appear in the output with double-quotes. - * - * ### Scripting Flag - * - * The Tag Processor parses HTML with the "scripting flag" disabled. This means - * that it doesn't run any scripts while parsing the page. In a browser with - * JavaScript enabled, for example, the script can change the parse of the - * document as it loads. On the server, however, evaluating JavaScript is not - * only impractical, but also unwanted. - * - * Practically this means that the Tag Processor will descend into NOSCRIPT - * elements and process its child tags. Were the scripting flag enabled, such - * as in a typical browser, the contents of NOSCRIPT are skipped entirely. - * - * This allows the HTML API to process the content that will be presented in - * a browser when scripting is disabled, but it offers a different view of a - * page than most browser sessions will experience. E.g. the tags inside the - * NOSCRIPT disappear. - * - * ### Text Encoding - * - * The Tag Processor assumes that the input HTML document is encoded with a - * text encoding compatible with 7-bit ASCII's '<', '>', '&', ';', '/', '=', - * "'", '"', 'a' - 'z', 'A' - 'Z', and the whitespace characters ' ', tab, - * carriage-return, newline, and form-feed. - * - * In practice, this includes almost every single-byte encoding as well as - * UTF-8. Notably, however, it does not include UTF-16. If providing input - * that's incompatible, then convert the encoding beforehand. - * - * @since 6.2.0 - * @since 6.2.1 Fix: Support for various invalid comments; attribute updates are case-insensitive. - * @since 6.3.2 Fix: Skip HTML-like content inside rawtext elements such as STYLE. - * @since 6.5.0 Pauses processor when input ends in an incomplete syntax token. - * Introduces "special" elements which act like void elements, e.g. TITLE, STYLE. - * Allows scanning through all tokens and processing modifiable text, where applicable. - */ -class Gutenberg_HTML_Tag_Processor_6_6 { - /** - * The maximum number of bookmarks allowed to exist at - * any given time. - * - * @since 6.2.0 - * @var int - * - * @see WP_HTML_Tag_Processor::set_bookmark() - */ - const MAX_BOOKMARKS = 10; - - /** - * Maximum number of times seek() can be called. - * Prevents accidental infinite loops. - * - * @since 6.2.0 - * @var int - * - * @see WP_HTML_Tag_Processor::seek() - */ - const MAX_SEEK_OPS = 1000; - - /** - * The HTML document to parse. - * - * @since 6.2.0 - * @var string - */ - protected $html; - - /** - * The last query passed to next_tag(). - * - * @since 6.2.0 - * @var array|null - */ - private $last_query; - - /** - * The tag name this processor currently scans for. - * - * @since 6.2.0 - * @var string|null - */ - private $sought_tag_name; - - /** - * The CSS class name this processor currently scans for. - * - * @since 6.2.0 - * @var string|null - */ - private $sought_class_name; - - /** - * The match offset this processor currently scans for. - * - * @since 6.2.0 - * @var int|null - */ - private $sought_match_offset; - - /** - * Whether to visit tag closers, e.g.
, when walking an input document. - * - * @since 6.2.0 - * @var bool - */ - private $stop_on_tag_closers; - - /** - * Specifies mode of operation of the parser at any given time. - * - * | State | Meaning | - * | ----------------|----------------------------------------------------------------------| - * | *Ready* | The parser is ready to run. | - * | *Complete* | There is nothing left to parse. | - * | *Incomplete* | The HTML ended in the middle of a token; nothing more can be parsed. | - * | *Matched tag* | Found an HTML tag; it's possible to modify its attributes. | - * | *Text node* | Found a #text node; this is plaintext and modifiable. | - * | *CDATA node* | Found a CDATA section; this is modifiable. | - * | *Comment* | Found a comment or bogus comment; this is modifiable. | - * | *Presumptuous* | Found an empty tag closer: ``. | - * | *Funky comment* | Found a tag closer with an invalid tag name; this is modifiable. | - * - * @since 6.5.0 - * - * @see WP_HTML_Tag_Processor::STATE_READY - * @see WP_HTML_Tag_Processor::STATE_COMPLETE - * @see WP_HTML_Tag_Processor::STATE_INCOMPLETE_INPUT - * @see WP_HTML_Tag_Processor::STATE_MATCHED_TAG - * @see WP_HTML_Tag_Processor::STATE_TEXT_NODE - * @see WP_HTML_Tag_Processor::STATE_CDATA_NODE - * @see WP_HTML_Tag_Processor::STATE_COMMENT - * @see WP_HTML_Tag_Processor::STATE_DOCTYPE - * @see WP_HTML_Tag_Processor::STATE_PRESUMPTUOUS_TAG - * @see WP_HTML_Tag_Processor::STATE_FUNKY_COMMENT - * - * @var string - */ - protected $parser_state = self::STATE_READY; - - /** - * What kind of syntax token became an HTML comment. - * - * Since there are many ways in which HTML syntax can create an HTML comment, - * this indicates which of those caused it. This allows the Tag Processor to - * represent more from the original input document than would appear in the DOM. - * - * @since 6.5.0 - * - * @var string|null - */ - protected $comment_type = null; - - /** - * How many bytes from the original HTML document have been read and parsed. - * - * This value points to the latest byte offset in the input document which - * has been already parsed. It is the internal cursor for the Tag Processor - * and updates while scanning through the HTML tokens. - * - * @since 6.2.0 - * @var int - */ - private $bytes_already_parsed = 0; - - /** - * Byte offset in input document where current token starts. - * - * Example: - * - *
... - * 01234 - * - token starts at 0 - * - * @since 6.5.0 - * - * @var int|null - */ - private $token_starts_at; - - /** - * Byte length of current token. - * - * Example: - * - *
... - * 012345678901234 - * - token length is 14 - 0 = 14 - * - * a is a token. - * 0123456789 123456789 123456789 - * - token length is 17 - 2 = 15 - * - * @since 6.5.0 - * - * @var int|null - */ - private $token_length; - - /** - * Byte offset in input document where current tag name starts. - * - * Example: - * - *
... - * 01234 - * - tag name starts at 1 - * - * @since 6.2.0 - * - * @var int|null - */ - private $tag_name_starts_at; - - /** - * Byte length of current tag name. - * - * Example: - * - *
... - * 01234 - * --- tag name length is 3 - * - * @since 6.2.0 - * - * @var int|null - */ - private $tag_name_length; - - /** - * Byte offset into input document where current modifiable text starts. - * - * @since 6.5.0 - * - * @var int - */ - private $text_starts_at; - - /** - * Byte length of modifiable text. - * - * @since 6.5.0 - * - * @var string - */ - private $text_length; - - /** - * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
. - * - * @var bool - */ - private $is_closing_tag; - - /** - * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. - * - * Example: - * - * // Supposing the parser is working through this content - * // and stops after recognizing the `id` attribute. - * //
- * // ^ parsing will continue from this point. - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Token( 'id', 9, 6, 5, 11, false ) - * ); - * - * // When picking up parsing again, or when asking to find the - * // `class` attribute we will continue and add to this array. - * $this->attributes = array( - * 'id' => new WP_HTML_Attribute_Token( 'id', 9, 6, 5, 11, false ), - * 'class' => new WP_HTML_Attribute_Token( 'class', 23, 7, 17, 13, false ) - * ); - * - * // Note that only the `class` attribute value is stored in the index. - * // That's because it is the only value used by this class at the moment. - * - * @since 6.2.0 - * @var WP_HTML_Attribute_Token[] - */ - private $attributes = array(); - - /** - * Tracks spans of duplicate attributes on a given tag, used for removing - * all copies of an attribute when calling `remove_attribute()`. - * - * @since 6.3.2 - * - * @var (WP_HTML_Span[])[]|null - */ - private $duplicate_attributes = null; - - /** - * Which class names to add or remove from a tag. - * - * These are tracked separately from attribute updates because they are - * semantically distinct, whereas this interface exists for the common - * case of adding and removing class names while other attributes are - * generally modified as with DOM `setAttribute` calls. - * - * When modifying an HTML document these will eventually be collapsed - * into a single `set_attribute( 'class', $changes )` call. - * - * Example: - * - * // Add the `wp-block-group` class, remove the `wp-group` class. - * $classname_updates = array( - * // Indexed by a comparable class name. - * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS, - * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS - * ); - * - * @since 6.2.0 - * @var bool[] - */ - private $classname_updates = array(); - - /** - * Tracks a semantic location in the original HTML which - * shifts with updates as they are applied to the document. - * - * @since 6.2.0 - * @var WP_HTML_Span[] - */ - protected $bookmarks = array(); - - const ADD_CLASS = true; - const REMOVE_CLASS = false; - const SKIP_CLASS = null; - - /** - * Lexical replacements to apply to input HTML document. - * - * "Lexical" in this class refers to the part of this class which - * operates on pure text _as text_ and not as HTML. There's a line - * between the public interface, with HTML-semantic methods like - * `set_attribute` and `add_class`, and an internal state that tracks - * text offsets in the input document. - * - * When higher-level HTML methods are called, those have to transform their - * operations (such as setting an attribute's value) into text diffing - * operations (such as replacing the sub-string from indices A to B with - * some given new string). These text-diffing operations are the lexical - * updates. - * - * As new higher-level methods are added they need to collapse their - * operations into these lower-level lexical updates since that's the - * Tag Processor's internal language of change. Any code which creates - * these lexical updates must ensure that they do not cross HTML syntax - * boundaries, however, so these should never be exposed outside of this - * class or any classes which intentionally expand its functionality. - * - * These are enqueued while editing the document instead of being immediately - * applied to avoid processing overhead, string allocations, and string - * copies when applying many updates to a single document. - * - * Example: - * - * // Replace an attribute stored with a new value, indices - * // sourced from the lazily-parsed HTML recognizer. - * $start = $attributes['src']->start; - * $length = $attributes['src']->length; - * $modifications[] = new WP_HTML_Text_Replacement( $start, $length, $new_value ); - * - * // Correspondingly, something like this will appear in this array. - * $lexical_updates = array( - * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' ) - * ); - * - * @since 6.2.0 - * @var WP_HTML_Text_Replacement[] - */ - protected $lexical_updates = array(); - - /** - * Tracks and limits `seek()` calls to prevent accidental infinite loops. - * - * @since 6.2.0 - * @var int - * - * @see WP_HTML_Tag_Processor::seek() - */ - protected $seek_count = 0; - - /** - * Constructor. - * - * @since 6.2.0 - * - * @param string $html HTML to process. - */ - public function __construct( $html ) { - $this->html = $html; - } - - /** - * Finds the next tag matching the $query. - * - * @since 6.2.0 - * @since 6.5.0 No longer processes incomplete tokens at end of document; pauses the processor at start of token. - * - * @param array|string|null $query { - * Optional. Which tag name to find, having which class, etc. Default is to find any tag. - * - * @type string|null $tag_name Which tag to find, or `null` for "any tag." - * @type int|null $match_offset Find the Nth tag matching all search criteria. - * 1 for "first" tag, 3 for "third," etc. - * Defaults to first tag. - * @type string|null $class_name Tag must contain this whole class name to match. - * @type string|null $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
. - * } - * @return bool Whether a tag was matched. - */ - public function next_tag( $query = null ) { - $this->parse_query( $query ); - $already_found = 0; - - do { - if ( false === $this->next_token() ) { - return false; - } - - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - continue; - } - - if ( $this->matches() ) { - ++$already_found; - } - } while ( $already_found < $this->sought_match_offset ); - - return true; - } - - /** - * Finds the next token in the HTML document. - * - * An HTML document can be viewed as a stream of tokens, - * where tokens are things like HTML tags, HTML comments, - * text nodes, etc. This method finds the next token in - * the HTML document and returns whether it found one. - * - * If it starts parsing a token and reaches the end of the - * document then it will seek to the start of the last - * token and pause, returning `false` to indicate that it - * failed to find a complete token. - * - * Possible token types, based on the HTML specification: - * - * - an HTML tag, whether opening, closing, or void. - * - a text node - the plaintext inside tags. - * - an HTML comment. - * - a DOCTYPE declaration. - * - a processing instruction, e.g. ``. - * - * The Tag Processor currently only supports the tag token. - * - * @since 6.5.0 - * - * @return bool Whether a token was parsed. - */ - public function next_token() { - return $this->base_class_next_token(); - } - - /** - * Internal method which finds the next token in the HTML document. - * - * This method is a protected internal function which implements the logic for - * finding the next token in a document. It exists so that the parser can update - * its state without affecting the location of the cursor in the document and - * without triggering subclass methods for things like `next_token()`, e.g. when - * applying patches before searching for the next token. - * - * @since 6.5.0 - * - * @access private - * - * @return bool Whether a token was parsed. - */ - private function base_class_next_token() { - $was_at = $this->bytes_already_parsed; - $this->after_tag(); - - // Don't proceed if there's nothing more to scan. - if ( - self::STATE_COMPLETE === $this->parser_state || - self::STATE_INCOMPLETE_INPUT === $this->parser_state - ) { - return false; - } - - /* - * The next step in the parsing loop determines the parsing state; - * clear it so that state doesn't linger from the previous step. - */ - $this->parser_state = self::STATE_READY; - - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_COMPLETE; - return false; - } - - // Find the next tag if it exists. - if ( false === $this->parse_next_tag() ) { - if ( self::STATE_INCOMPLETE_INPUT === $this->parser_state ) { - $this->bytes_already_parsed = $was_at; - } - - return false; - } - - /* - * For legacy reasons the rest of this function handles tags and their - * attributes. If the processor has reached the end of the document - * or if it matched any other token then it should return here to avoid - * attempting to process tag-specific syntax. - */ - if ( - self::STATE_INCOMPLETE_INPUT !== $this->parser_state && - self::STATE_COMPLETE !== $this->parser_state && - self::STATE_MATCHED_TAG !== $this->parser_state - ) { - return true; - } - - // Parse all of its attributes. - while ( $this->parse_next_attribute() ) { - continue; - } - - // Ensure that the tag closes before the end of the document. - if ( - self::STATE_INCOMPLETE_INPUT === $this->parser_state || - $this->bytes_already_parsed >= strlen( $this->html ) - ) { - // Does this appropriately clear state (parsed attributes)? - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - $this->bytes_already_parsed = $was_at; - - return false; - } - - $tag_ends_at = strpos( $this->html, '>', $this->bytes_already_parsed ); - if ( false === $tag_ends_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - $this->bytes_already_parsed = $was_at; - - return false; - } - $this->parser_state = self::STATE_MATCHED_TAG; - $this->bytes_already_parsed = $tag_ends_at + 1; - $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; - - /* - * For non-DATA sections which might contain text that looks like HTML tags but - * isn't, scan with the appropriate alternative mode. Looking at the first letter - * of the tag name as a pre-check avoids a string allocation when it's not needed. - */ - $t = $this->html[ $this->tag_name_starts_at ]; - if ( - $this->is_closing_tag || - ! ( - 'i' === $t || 'I' === $t || - 'n' === $t || 'N' === $t || - 's' === $t || 'S' === $t || - 't' === $t || 'T' === $t || - 'x' === $t || 'X' === $t - ) - ) { - return true; - } - - $tag_name = $this->get_tag(); - - /* - * Preserve the opening tag pointers, as these will be overwritten - * when finding the closing tag. They will be reset after finding - * the closing to tag to point to the opening of the special atomic - * tag sequence. - */ - $tag_name_starts_at = $this->tag_name_starts_at; - $tag_name_length = $this->tag_name_length; - $tag_ends_at = $this->token_starts_at + $this->token_length; - $attributes = $this->attributes; - $duplicate_attributes = $this->duplicate_attributes; - - // Find the closing tag if necessary. - $found_closer = false; - switch ( $tag_name ) { - case 'SCRIPT': - $found_closer = $this->skip_script_data(); - break; - - case 'TEXTAREA': - case 'TITLE': - $found_closer = $this->skip_rcdata( $tag_name ); - break; - - /* - * In the browser this list would include the NOSCRIPT element, - * but the Tag Processor is an environment with the scripting - * flag disabled, meaning that it needs to descend into the - * NOSCRIPT element to be able to properly process what will be - * sent to a browser. - * - * Note that this rule makes HTML5 syntax incompatible with XML, - * because the parsing of this token depends on client application. - * The NOSCRIPT element cannot be represented in the XHTML syntax. - */ - case 'IFRAME': - case 'NOEMBED': - case 'NOFRAMES': - case 'STYLE': - case 'XMP': - $found_closer = $this->skip_rawtext( $tag_name ); - break; - - // No other tags should be treated in their entirety here. - default: - return true; - } - - if ( ! $found_closer ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - $this->bytes_already_parsed = $was_at; - return false; - } - - /* - * The values here look like they reference the opening tag but they reference - * the closing tag instead. This is why the opening tag values were stored - * above in a variable. It reads confusingly here, but that's because the - * functions that skip the contents have moved all the internal cursors past - * the inner content of the tag. - */ - $this->token_starts_at = $was_at; - $this->token_length = $this->bytes_already_parsed - $this->token_starts_at; - $this->text_starts_at = $tag_ends_at; - $this->text_length = $this->tag_name_starts_at - $this->text_starts_at; - $this->tag_name_starts_at = $tag_name_starts_at; - $this->tag_name_length = $tag_name_length; - $this->attributes = $attributes; - $this->duplicate_attributes = $duplicate_attributes; - - return true; - } - - /** - * Whether the processor paused because the input HTML document ended - * in the middle of a syntax element, such as in the middle of a tag. - * - * Example: - * - * $processor = new WP_HTML_Tag_Processor( '" ); - * $p->next_tag(); - * foreach ( $p->class_list() as $class_name ) { - * echo "{$class_name} "; - * } - * // Outputs: "free lang-en " - * - * @since 6.4.0 - */ - public function class_list() { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return; - } - - /** @var string $class contains the string value of the class attribute, with character references decoded. */ - $class = $this->get_attribute( 'class' ); - - if ( ! is_string( $class ) ) { - return; - } - - $seen = array(); - - $at = 0; - while ( $at < strlen( $class ) ) { - // Skip past any initial boundary characters. - $at += strspn( $class, " \t\f\r\n", $at ); - if ( $at >= strlen( $class ) ) { - return; - } - - // Find the byte length until the next boundary. - $length = strcspn( $class, " \t\f\r\n", $at ); - if ( 0 === $length ) { - return; - } - - /* - * CSS class names are case-insensitive in the ASCII range. - * - * @see https://www.w3.org/TR/CSS2/syndata.html#x1 - */ - $name = strtolower( substr( $class, $at, $length ) ); - $at += $length; - - /* - * It's expected that the number of class names for a given tag is relatively small. - * Given this, it is probably faster overall to scan an array for a value rather - * than to use the class name as a key and check if it's a key of $seen. - */ - if ( in_array( $name, $seen, true ) ) { - continue; - } - - $seen[] = $name; - yield $name; - } - } - - - /** - * Returns if a matched tag contains the given ASCII case-insensitive class name. - * - * @since 6.4.0 - * - * @param string $wanted_class Look for this CSS class name, ASCII case-insensitive. - * @return bool|null Whether the matched tag contains the given class name, or null if not matched. - */ - public function has_class( $wanted_class ) { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return null; - } - - $wanted_class = strtolower( $wanted_class ); - - foreach ( $this->class_list() as $class_name ) { - if ( $class_name === $wanted_class ) { - return true; - } - } - - return false; - } - - - /** - * Sets a bookmark in the HTML document. - * - * Bookmarks represent specific places or tokens in the HTML - * document, such as a tag opener or closer. When applying - * edits to a document, such as setting an attribute, the - * text offsets of that token may shift; the bookmark is - * kept updated with those shifts and remains stable unless - * the entire span of text in which the token sits is removed. - * - * Release bookmarks when they are no longer needed. - * - * Example: - * - *

Surprising fact you may not know!

- * ^ ^ - * \-|-- this `H2` opener bookmark tracks the token - * - *

Surprising fact you may no… - * ^ ^ - * \-|-- it shifts with edits - * - * Bookmarks provide the ability to seek to a previously-scanned - * place in the HTML document. This avoids the need to re-scan - * the entire document. - * - * Example: - * - *
  • One
  • Two
  • Three
- * ^^^^ - * want to note this last item - * - * $p = new WP_HTML_Tag_Processor( $html ); - * $in_list = false; - * while ( $p->next_tag( array( 'tag_closers' => $in_list ? 'visit' : 'skip' ) ) ) { - * if ( 'UL' === $p->get_tag() ) { - * if ( $p->is_tag_closer() ) { - * $in_list = false; - * $p->set_bookmark( 'resume' ); - * if ( $p->seek( 'last-li' ) ) { - * $p->add_class( 'last-li' ); - * } - * $p->seek( 'resume' ); - * $p->release_bookmark( 'last-li' ); - * $p->release_bookmark( 'resume' ); - * } else { - * $in_list = true; - * } - * } - * - * if ( 'LI' === $p->get_tag() ) { - * $p->set_bookmark( 'last-li' ); - * } - * } - * - * Bookmarks intentionally hide the internal string offsets - * to which they refer. They are maintained internally as - * updates are applied to the HTML document and therefore - * retain their "position" - the location to which they - * originally pointed. The inability to use bookmarks with - * functions like `substr` is therefore intentional to guard - * against accidentally breaking the HTML. - * - * Because bookmarks allocate memory and require processing - * for every applied update, they are limited and require - * a name. They should not be created with programmatically-made - * names, such as "li_{$index}" with some loop. As a general - * rule they should only be created with string-literal names - * like "start-of-section" or "last-paragraph". - * - * Bookmarks are a powerful tool to enable complicated behavior. - * Consider double-checking that you need this tool if you are - * reaching for it, as inappropriate use could lead to broken - * HTML structure or unwanted processing overhead. - * - * @since 6.2.0 - * - * @param string $name Identifies this particular bookmark. - * @return bool Whether the bookmark was successfully created. - */ - public function set_bookmark( $name ) { - // It only makes sense to set a bookmark if the parser has paused on a concrete token. - if ( - self::STATE_COMPLETE === $this->parser_state || - self::STATE_INCOMPLETE_INPUT === $this->parser_state - ) { - return false; - } - - if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= static::MAX_BOOKMARKS ) { - _doing_it_wrong( - __METHOD__, - __( 'Too many bookmarks: cannot create any more.' ), - '6.2.0' - ); - return false; - } - - $this->bookmarks[ $name ] = new WP_HTML_Span( $this->token_starts_at, $this->token_length ); - - return true; - } - - - /** - * Removes a bookmark that is no longer needed. - * - * Releasing a bookmark frees up the small - * performance overhead it requires. - * - * @param string $name Name of the bookmark to remove. - * @return bool Whether the bookmark already existed before removal. - */ - public function release_bookmark( $name ) { - if ( ! array_key_exists( $name, $this->bookmarks ) ) { - return false; - } - - unset( $this->bookmarks[ $name ] ); - - return true; - } - - /** - * Skips contents of generic rawtext elements. - * - * @since 6.3.2 - * - * @see https://html.spec.whatwg.org/#generic-raw-text-element-parsing-algorithm - * - * @param string $tag_name The uppercase tag name which will close the RAWTEXT region. - * @return bool Whether an end to the RAWTEXT region was found before the end of the document. - */ - private function skip_rawtext( $tag_name ) { - /* - * These two functions distinguish themselves on whether character references are - * decoded, and since functionality to read the inner markup isn't supported, it's - * not necessary to implement these two functions separately. - */ - return $this->skip_rcdata( $tag_name ); - } - - /** - * Skips contents of RCDATA elements, namely title and textarea tags. - * - * @since 6.2.0 - * - * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state - * - * @param string $tag_name The uppercase tag name which will close the RCDATA region. - * @return bool Whether an end to the RCDATA region was found before the end of the document. - */ - private function skip_rcdata( $tag_name ) { - $html = $this->html; - $doc_length = strlen( $html ); - $tag_length = strlen( $tag_name ); - - $at = $this->bytes_already_parsed; - - while ( false !== $at && $at < $doc_length ) { - $at = strpos( $this->html, 'tag_name_starts_at = $at; - - // Fail if there is no possible tag closer. - if ( false === $at || ( $at + $tag_length ) >= $doc_length ) { - return false; - } - - $at += 2; - - /* - * Find a case-insensitive match to the tag name. - * - * Because tag names are limited to US-ASCII there is no - * need to perform any kind of Unicode normalization when - * comparing; any character which could be impacted by such - * normalization could not be part of a tag name. - */ - for ( $i = 0; $i < $tag_length; $i++ ) { - $tag_char = $tag_name[ $i ]; - $html_char = $html[ $at + $i ]; - - if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) { - $at += $i; - continue 2; - } - } - - $at += $tag_length; - $this->bytes_already_parsed = $at; - - if ( $at >= strlen( $html ) ) { - return false; - } - - /* - * Ensure that the tag name terminates to avoid matching on - * substrings of a longer tag name. For example, the sequence - * "' !== $c ) { - continue; - } - - while ( $this->parse_next_attribute() ) { - continue; - } - - $at = $this->bytes_already_parsed; - if ( $at >= strlen( $this->html ) ) { - return false; - } - - if ( '>' === $html[ $at ] ) { - $this->bytes_already_parsed = $at + 1; - return true; - } - - if ( $at + 1 >= strlen( $this->html ) ) { - return false; - } - - if ( '/' === $html[ $at ] && '>' === $html[ $at + 1 ] ) { - $this->bytes_already_parsed = $at + 2; - return true; - } - } - - return false; - } - - /** - * Skips contents of script tags. - * - * @since 6.2.0 - * - * @return bool Whether the script tag was closed before the end of the document. - */ - private function skip_script_data() { - $state = 'unescaped'; - $html = $this->html; - $doc_length = strlen( $html ); - $at = $this->bytes_already_parsed; - - while ( false !== $at && $at < $doc_length ) { - $at += strcspn( $html, '-<', $at ); - - /* - * For all script states a "-->" transitions - * back into the normal unescaped script mode, - * even if that's the current state. - */ - if ( - $at + 2 < $doc_length && - '-' === $html[ $at ] && - '-' === $html[ $at + 1 ] && - '>' === $html[ $at + 2 ] - ) { - $at += 3; - $state = 'unescaped'; - continue; - } - - // Everything of interest past here starts with "<". - if ( $at + 1 >= $doc_length || '<' !== $html[ $at++ ] ) { - continue; - } - - /* - * Unlike with "-->", the "`. Unlike other comment - * and bogus comment syntax, these leave no clear insertion point for text and - * they need to be modified specially in order to contain text. E.g. to store - * `?` as the modifiable text, the `` needs to become ``, which - * involves inserting an additional `-` into the token after the modifiable text. - */ - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT; - $this->token_length = $closer_at + $span_of_dashes + 1 - $this->token_starts_at; - - // Only provide modifiable text if the token is long enough to contain it. - if ( $span_of_dashes >= 2 ) { - $this->comment_type = self::COMMENT_AS_HTML_COMMENT; - $this->text_starts_at = $this->token_starts_at + 4; - $this->text_length = $span_of_dashes - 2; - } - - $this->bytes_already_parsed = $closer_at + $span_of_dashes + 1; - return true; - } - - /* - * Comments may be closed by either a --> or an invalid --!>. - * The first occurrence closes the comment. - * - * See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment - */ - --$closer_at; // Pre-increment inside condition below reduces risk of accidental infinite looping. - while ( ++$closer_at < $doc_length ) { - $closer_at = strpos( $html, '--', $closer_at ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - if ( $closer_at + 2 < $doc_length && '>' === $html[ $closer_at + 2 ] ) { - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_HTML_COMMENT; - $this->token_length = $closer_at + 3 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 4; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 3; - return true; - } - - if ( - $closer_at + 3 < $doc_length && - '!' === $html[ $closer_at + 2 ] && - '>' === $html[ $closer_at + 3 ] - ) { - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_HTML_COMMENT; - $this->token_length = $closer_at + 4 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 4; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 4; - return true; - } - } - } - - /* - * ` - * These are ASCII-case-insensitive. - * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state - */ - if ( - $doc_length > $at + 8 && - ( 'D' === $html[ $at + 2 ] || 'd' === $html[ $at + 2 ] ) && - ( 'O' === $html[ $at + 3 ] || 'o' === $html[ $at + 3 ] ) && - ( 'C' === $html[ $at + 4 ] || 'c' === $html[ $at + 4 ] ) && - ( 'T' === $html[ $at + 5 ] || 't' === $html[ $at + 5 ] ) && - ( 'Y' === $html[ $at + 6 ] || 'y' === $html[ $at + 6 ] ) && - ( 'P' === $html[ $at + 7 ] || 'p' === $html[ $at + 7 ] ) && - ( 'E' === $html[ $at + 8 ] || 'e' === $html[ $at + 8 ] ) - ) { - $closer_at = strpos( $html, '>', $at + 9 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_DOCTYPE; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 9; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - return true; - } - - /* - * Anything else here is an incorrectly-opened comment and transitions - * to the bogus comment state - skip to the nearest >. If no closer is - * found then the HTML was truncated inside the markup declaration. - */ - $closer_at = strpos( $html, '>', $at + 1 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_INVALID_HTML; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 2; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - - /* - * Identify nodes that would be CDATA if HTML had CDATA sections. - * - * This section must occur after identifying the bogus comment end - * because in an HTML parser it will span to the nearest `>`, even - * if there's no `]]>` as would be required in an XML document. It - * is therefore not possible to parse a CDATA section containing - * a `>` in the HTML syntax. - * - * Inside foreign elements there is a discrepancy between browsers - * and the specification on this. - * - * @todo Track whether the Tag Processor is inside a foreign element - * and require the proper closing `]]>` in those cases. - */ - if ( - $this->token_length >= 10 && - '[' === $html[ $this->token_starts_at + 2 ] && - 'C' === $html[ $this->token_starts_at + 3 ] && - 'D' === $html[ $this->token_starts_at + 4 ] && - 'A' === $html[ $this->token_starts_at + 5 ] && - 'T' === $html[ $this->token_starts_at + 6 ] && - 'A' === $html[ $this->token_starts_at + 7 ] && - '[' === $html[ $this->token_starts_at + 8 ] && - ']' === $html[ $closer_at - 1 ] && - ']' === $html[ $closer_at - 2 ] - ) { - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_CDATA_LOOKALIKE; - $this->text_starts_at += 7; - $this->text_length -= 9; - } - - return true; - } - - /* - * is a missing end tag name, which is ignored. - * - * This was also known as the "presumptuous empty tag" - * in early discussions as it was proposed to close - * the nearest previous opening tag. - * - * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name - */ - if ( '>' === $html[ $at + 1 ] ) { - // `<>` is interpreted as plaintext. - if ( ! $this->is_closing_tag ) { - ++$at; - continue; - } - - $this->parser_state = self::STATE_PRESUMPTUOUS_TAG; - $this->token_length = $at + 2 - $this->token_starts_at; - $this->bytes_already_parsed = $at + 2; - return true; - } - - /* - * ` - * See https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state - */ - if ( ! $this->is_closing_tag && '?' === $html[ $at + 1 ] ) { - $closer_at = strpos( $html, '>', $at + 2 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_COMMENT; - $this->comment_type = self::COMMENT_AS_INVALID_HTML; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 2; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - - /* - * Identify a Processing Instruction node were HTML to have them. - * - * This section must occur after identifying the bogus comment end - * because in an HTML parser it will span to the nearest `>`, even - * if there's no `?>` as would be required in an XML document. It - * is therefore not possible to parse a Processing Instruction node - * containing a `>` in the HTML syntax. - * - * XML allows for more target names, but this code only identifies - * those with ASCII-representable target names. This means that it - * may identify some Processing Instruction nodes as bogus comments, - * but it will not misinterpret the HTML structure. By limiting the - * identification to these target names the Tag Processor can avoid - * the need to start parsing UTF-8 sequences. - * - * > NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | - * [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | - * [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | - * [#x10000-#xEFFFF] - * > NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] - * - * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#NT-PITarget - */ - if ( $this->token_length >= 5 && '?' === $html[ $closer_at - 1 ] ) { - $comment_text = substr( $html, $this->token_starts_at + 2, $this->token_length - 4 ); - $pi_target_length = strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:_' ); - - if ( 0 < $pi_target_length ) { - $pi_target_length += strspn( $comment_text, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:_-.', $pi_target_length ); - - $this->comment_type = self::COMMENT_AS_PI_NODE_LOOKALIKE; - $this->tag_name_starts_at = $this->token_starts_at + 2; - $this->tag_name_length = $pi_target_length; - $this->text_starts_at += $pi_target_length; - $this->text_length -= $pi_target_length + 1; - } - } - - return true; - } - - /* - * If a non-alpha starts the tag name in a tag closer it's a comment. - * Find the first `>`, which closes the comment. - * - * This parser classifies these particular comments as special "funky comments" - * which are made available for further processing. - * - * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name - */ - if ( $this->is_closing_tag ) { - // No chance of finding a closer. - if ( $at + 3 > $doc_length ) { - return false; - } - - $closer_at = strpos( $html, '>', $at + 2 ); - if ( false === $closer_at ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->parser_state = self::STATE_FUNKY_COMMENT; - $this->token_length = $closer_at + 1 - $this->token_starts_at; - $this->text_starts_at = $this->token_starts_at + 2; - $this->text_length = $closer_at - $this->text_starts_at; - $this->bytes_already_parsed = $closer_at + 1; - return true; - } - - ++$at; - } - - return false; - } - - /** - * Parses the next attribute. - * - * @since 6.2.0 - * - * @return bool Whether an attribute was found before the end of the document. - */ - private function parse_next_attribute() { - // Skip whitespace and slashes. - $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n/", $this->bytes_already_parsed ); - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - /* - * Treat the equal sign as a part of the attribute - * name if it is the first encountered byte. - * - * @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state - */ - $name_length = '=' === $this->html[ $this->bytes_already_parsed ] - ? 1 + strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed + 1 ) - : strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed ); - - // No attribute, just tag closer. - if ( 0 === $name_length || $this->bytes_already_parsed + $name_length >= strlen( $this->html ) ) { - return false; - } - - $attribute_start = $this->bytes_already_parsed; - $attribute_name = substr( $this->html, $attribute_start, $name_length ); - $this->bytes_already_parsed += $name_length; - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $this->skip_whitespace(); - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - $has_value = '=' === $this->html[ $this->bytes_already_parsed ]; - if ( $has_value ) { - ++$this->bytes_already_parsed; - $this->skip_whitespace(); - if ( $this->bytes_already_parsed >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - switch ( $this->html[ $this->bytes_already_parsed ] ) { - case "'": - case '"': - $quote = $this->html[ $this->bytes_already_parsed ]; - $value_start = $this->bytes_already_parsed + 1; - $value_length = strcspn( $this->html, $quote, $value_start ); - $attribute_end = $value_start + $value_length + 1; - $this->bytes_already_parsed = $attribute_end; - break; - - default: - $value_start = $this->bytes_already_parsed; - $value_length = strcspn( $this->html, "> \t\f\r\n", $value_start ); - $attribute_end = $value_start + $value_length; - $this->bytes_already_parsed = $attribute_end; - } - } else { - $value_start = $this->bytes_already_parsed; - $value_length = 0; - $attribute_end = $attribute_start + $name_length; - } - - if ( $attribute_end >= strlen( $this->html ) ) { - $this->parser_state = self::STATE_INCOMPLETE_INPUT; - - return false; - } - - if ( $this->is_closing_tag ) { - return true; - } - - /* - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - */ - $comparable_name = strtolower( $attribute_name ); - - // If an attribute is listed many times, only use the first declaration and ignore the rest. - if ( ! array_key_exists( $comparable_name, $this->attributes ) ) { - $this->attributes[ $comparable_name ] = new WP_HTML_Attribute_Token( - $attribute_name, - $value_start, - $value_length, - $attribute_start, - $attribute_end - $attribute_start, - ! $has_value - ); - - return true; - } - - /* - * Track the duplicate attributes so if we remove it, all disappear together. - * - * While `$this->duplicated_attributes` could always be stored as an `array()`, - * which would simplify the logic here, storing a `null` and only allocating - * an array when encountering duplicates avoids needless allocations in the - * normative case of parsing tags with no duplicate attributes. - */ - $duplicate_span = new WP_HTML_Span( $attribute_start, $attribute_end - $attribute_start ); - if ( null === $this->duplicate_attributes ) { - $this->duplicate_attributes = array( $comparable_name => array( $duplicate_span ) ); - } elseif ( ! array_key_exists( $comparable_name, $this->duplicate_attributes ) ) { - $this->duplicate_attributes[ $comparable_name ] = array( $duplicate_span ); - } else { - $this->duplicate_attributes[ $comparable_name ][] = $duplicate_span; - } - - return true; - } - - /** - * Move the internal cursor past any immediate successive whitespace. - * - * @since 6.2.0 - */ - private function skip_whitespace() { - $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n", $this->bytes_already_parsed ); - } - - /** - * Applies attribute updates and cleans up once a tag is fully parsed. - * - * @since 6.2.0 - */ - private function after_tag() { - /* - * There could be lexical updates enqueued for an attribute that - * also exists on the next tag. In order to avoid conflating the - * attributes across the two tags, lexical updates with names - * need to be flushed to raw lexical updates. - */ - $this->class_name_updates_to_attributes_updates(); - - /* - * Purge updates if there are too many. The actual count isn't - * scientific, but a few values from 100 to a few thousand were - * tests to find a practically-useful limit. - * - * If the update queue grows too big, then the Tag Processor - * will spend more time iterating through them and lose the - * efficiency gains of deferring applying them. - */ - if ( 1000 < count( $this->lexical_updates ) ) { - $this->get_updated_html(); - } - - foreach ( $this->lexical_updates as $name => $update ) { - /* - * Any updates appearing after the cursor should be applied - * before proceeding, otherwise they may be overlooked. - */ - if ( $update->start >= $this->bytes_already_parsed ) { - $this->get_updated_html(); - break; - } - - if ( is_int( $name ) ) { - continue; - } - - $this->lexical_updates[] = $update; - unset( $this->lexical_updates[ $name ] ); - } - - $this->token_starts_at = null; - $this->token_length = null; - $this->tag_name_starts_at = null; - $this->tag_name_length = null; - $this->text_starts_at = 0; - $this->text_length = 0; - $this->is_closing_tag = null; - $this->attributes = array(); - $this->comment_type = null; - $this->duplicate_attributes = null; - } - - /** - * Converts class name updates into tag attributes updates - * (they are accumulated in different data formats for performance). - * - * @since 6.2.0 - * - * @see WP_HTML_Tag_Processor::$lexical_updates - * @see WP_HTML_Tag_Processor::$classname_updates - */ - private function class_name_updates_to_attributes_updates() { - if ( count( $this->classname_updates ) === 0 ) { - return; - } - - $existing_class = $this->get_enqueued_attribute_value( 'class' ); - if ( null === $existing_class || true === $existing_class ) { - $existing_class = ''; - } - - if ( false === $existing_class && isset( $this->attributes['class'] ) ) { - $existing_class = substr( - $this->html, - $this->attributes['class']->value_starts_at, - $this->attributes['class']->value_length - ); - } - - if ( false === $existing_class ) { - $existing_class = ''; - } - - /** - * Updated "class" attribute value. - * - * This is incrementally built while scanning through the existing class - * attribute, skipping removed classes on the way, and then appending - * added classes at the end. Only when finished processing will the - * value contain the final new value. - - * @var string $class - */ - $class = ''; - - /** - * Tracks the cursor position in the existing - * class attribute value while parsing. - * - * @var int $at - */ - $at = 0; - - /** - * Indicates if there's any need to modify the existing class attribute. - * - * If a call to `add_class()` and `remove_class()` wouldn't impact - * the `class` attribute value then there's no need to rebuild it. - * For example, when adding a class that's already present or - * removing one that isn't. - * - * This flag enables a performance optimization when none of the enqueued - * class updates would impact the `class` attribute; namely, that the - * processor can continue without modifying the input document, as if - * none of the `add_class()` or `remove_class()` calls had been made. - * - * This flag is set upon the first change that requires a string update. - * - * @var bool $modified - */ - $modified = false; - - // Remove unwanted classes by only copying the new ones. - $existing_class_length = strlen( $existing_class ); - while ( $at < $existing_class_length ) { - // Skip to the first non-whitespace character. - $ws_at = $at; - $ws_length = strspn( $existing_class, " \t\f\r\n", $ws_at ); - $at += $ws_length; - - // Capture the class name – it's everything until the next whitespace. - $name_length = strcspn( $existing_class, " \t\f\r\n", $at ); - if ( 0 === $name_length ) { - // If no more class names are found then that's the end. - break; - } - - $name = substr( $existing_class, $at, $name_length ); - $at += $name_length; - - // If this class is marked for removal, start processing the next one. - $remove_class = ( - isset( $this->classname_updates[ $name ] ) && - self::REMOVE_CLASS === $this->classname_updates[ $name ] - ); - - // If a class has already been seen then skip it; it should not be added twice. - if ( ! $remove_class ) { - $this->classname_updates[ $name ] = self::SKIP_CLASS; - } - - if ( $remove_class ) { - $modified = true; - continue; - } - - /* - * Otherwise, append it to the new "class" attribute value. - * - * There are options for handling whitespace between tags. - * Preserving the existing whitespace produces fewer changes - * to the HTML content and should clarify the before/after - * content when debugging the modified output. - * - * This approach contrasts normalizing the inter-class - * whitespace to a single space, which might appear cleaner - * in the output HTML but produce a noisier change. - */ - $class .= substr( $existing_class, $ws_at, $ws_length ); - $class .= $name; - } - - // Add new classes by appending those which haven't already been seen. - foreach ( $this->classname_updates as $name => $operation ) { - if ( self::ADD_CLASS === $operation ) { - $modified = true; - - $class .= strlen( $class ) > 0 ? ' ' : ''; - $class .= $name; - } - } - - $this->classname_updates = array(); - if ( ! $modified ) { - return; - } - - if ( strlen( $class ) > 0 ) { - $this->set_attribute( 'class', $class ); - } else { - $this->remove_attribute( 'class' ); - } - } - - /** - * Applies attribute updates to HTML document. - * - * @since 6.2.0 - * @since 6.2.1 Accumulates shift for internal cursor and passed pointer. - * @since 6.3.0 Invalidate any bookmarks whose targets are overwritten. - * - * @param int $shift_this_point Accumulate and return shift for this position. - * @return int How many bytes the given pointer moved in response to the updates. - */ - private function apply_attributes_updates( $shift_this_point ) { - if ( ! count( $this->lexical_updates ) ) { - return 0; - } - - $accumulated_shift_for_given_point = 0; - - /* - * Attribute updates can be enqueued in any order but updates - * to the document must occur in lexical order; that is, each - * replacement must be made before all others which follow it - * at later string indices in the input document. - * - * Sorting avoid making out-of-order replacements which - * can lead to mangled output, partially-duplicated - * attributes, and overwritten attributes. - */ - usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) ); - - $bytes_already_copied = 0; - $output_buffer = ''; - foreach ( $this->lexical_updates as $diff ) { - $shift = strlen( $diff->text ) - $diff->length; - - // Adjust the cursor position by however much an update affects it. - if ( $diff->start < $this->bytes_already_parsed ) { - $this->bytes_already_parsed += $shift; - } - - // Accumulate shift of the given pointer within this function call. - if ( $diff->start <= $shift_this_point ) { - $accumulated_shift_for_given_point += $shift; - } - - $output_buffer .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied ); - $output_buffer .= $diff->text; - $bytes_already_copied = $diff->start + $diff->length; - } - - $this->html = $output_buffer . substr( $this->html, $bytes_already_copied ); - - /* - * Adjust bookmark locations to account for how the text - * replacements adjust offsets in the input document. - */ - foreach ( $this->bookmarks as $bookmark_name => $bookmark ) { - $bookmark_end = $bookmark->start + $bookmark->length; - - /* - * Each lexical update which appears before the bookmark's endpoints - * might shift the offsets for those endpoints. Loop through each change - * and accumulate the total shift for each bookmark, then apply that - * shift after tallying the full delta. - */ - $head_delta = 0; - $tail_delta = 0; - - foreach ( $this->lexical_updates as $diff ) { - $diff_end = $diff->start + $diff->length; - - if ( $bookmark->start < $diff->start && $bookmark_end < $diff->start ) { - break; - } - - if ( $bookmark->start >= $diff->start && $bookmark_end < $diff_end ) { - $this->release_bookmark( $bookmark_name ); - continue 2; - } - - $delta = strlen( $diff->text ) - $diff->length; - - if ( $bookmark->start >= $diff->start ) { - $head_delta += $delta; - } - - if ( $bookmark_end >= $diff_end ) { - $tail_delta += $delta; - } - } - - $bookmark->start += $head_delta; - $bookmark->length += $tail_delta - $head_delta; - } - - $this->lexical_updates = array(); - - return $accumulated_shift_for_given_point; - } - - /** - * Checks whether a bookmark with the given name exists. - * - * @since 6.3.0 - * - * @param string $bookmark_name Name to identify a bookmark that potentially exists. - * @return bool Whether that bookmark exists. - */ - public function has_bookmark( $bookmark_name ) { - return array_key_exists( $bookmark_name, $this->bookmarks ); - } - - /** - * Move the internal cursor in the Tag Processor to a given bookmark's location. - * - * In order to prevent accidental infinite loops, there's a - * maximum limit on the number of times seek() can be called. - * - * @since 6.2.0 - * - * @param string $bookmark_name Jump to the place in the document identified by this bookmark name. - * @return bool Whether the internal cursor was successfully moved to the bookmark's location. - */ - public function seek( $bookmark_name ) { - if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) { - _doing_it_wrong( - __METHOD__, - __( 'Unknown bookmark name.' ), - '6.2.0' - ); - return false; - } - - if ( ++$this->seek_count > static::MAX_SEEK_OPS ) { - _doing_it_wrong( - __METHOD__, - __( 'Too many calls to seek() - this can lead to performance issues.' ), - '6.2.0' - ); - return false; - } - - // Flush out any pending updates to the document. - $this->get_updated_html(); - - // Point this tag processor before the sought tag opener and consume it. - $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start; - $this->parser_state = self::STATE_READY; - return $this->next_token(); - } - - /** - * Compare two WP_HTML_Text_Replacement objects. - * - * @since 6.2.0 - * - * @param WP_HTML_Text_Replacement $a First attribute update. - * @param WP_HTML_Text_Replacement $b Second attribute update. - * @return int Comparison value for string order. - */ - private static function sort_start_ascending( $a, $b ) { - $by_start = $a->start - $b->start; - if ( 0 !== $by_start ) { - return $by_start; - } - - $by_text = isset( $a->text, $b->text ) ? strcmp( $a->text, $b->text ) : 0; - if ( 0 !== $by_text ) { - return $by_text; - } - - /* - * This code should be unreachable, because it implies the two replacements - * start at the same location and contain the same text. - */ - return $a->length - $b->length; - } - - /** - * Return the enqueued value for a given attribute, if one exists. - * - * Enqueued updates can take different data types: - * - If an update is enqueued and is boolean, the return will be `true` - * - If an update is otherwise enqueued, the return will be the string value of that update. - * - If an attribute is enqueued to be removed, the return will be `null` to indicate that. - * - If no updates are enqueued, the return will be `false` to differentiate from "removed." - * - * @since 6.2.0 - * - * @param string $comparable_name The attribute name in its comparable form. - * @return string|boolean|null Value of enqueued update if present, otherwise false. - */ - private function get_enqueued_attribute_value( $comparable_name ) { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return false; - } - - if ( ! isset( $this->lexical_updates[ $comparable_name ] ) ) { - return false; - } - - $enqueued_text = $this->lexical_updates[ $comparable_name ]->text; - - // Removed attributes erase the entire span. - if ( '' === $enqueued_text ) { - return null; - } - - /* - * Boolean attribute updates are just the attribute name without a corresponding value. - * - * This value might differ from the given comparable name in that there could be leading - * or trailing whitespace, and that the casing follows the name given in `set_attribute`. - * - * Example: - * - * $p->set_attribute( 'data-TEST-id', 'update' ); - * 'update' === $p->get_enqueued_attribute_value( 'data-test-id' ); - * - * Detect this difference based on the absence of the `=`, which _must_ exist in any - * attribute containing a value, e.g. ``. - * ¹ ² - * 1. Attribute with a string value. - * 2. Boolean attribute whose value is `true`. - */ - $equals_at = strpos( $enqueued_text, '=' ); - if ( false === $equals_at ) { - return true; - } - - /* - * Finally, a normal update's value will appear after the `=` and - * be double-quoted, as performed incidentally by `set_attribute`. - * - * e.g. `type="text"` - * ¹² ³ - * 1. Equals is here. - * 2. Double-quoting starts one after the equals sign. - * 3. Double-quoting ends at the last character in the update. - */ - $enqueued_value = substr( $enqueued_text, $equals_at + 2, -1 ); - return Gutenberg_HTML_Decoder_6_6::decode_attribute( $enqueued_value ); - } - - /** - * Returns the value of a requested attribute from a matched tag opener if that attribute exists. - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute( 'data-test-id' ) === '14'; - * $p->get_attribute( 'enabled' ) === true; - * $p->get_attribute( 'aria-label' ) === null; - * - * $p->next_tag() === false; - * $p->get_attribute( 'class' ) === null; - * - * @since 6.2.0 - * - * @param string $name Name of attribute whose value is requested. - * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`. - */ - public function get_attribute( $name ) { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return null; - } - - $comparable = strtolower( $name ); - - /* - * For every attribute other than `class` it's possible to perform a quick check if - * there's an enqueued lexical update whose value takes priority over what's found in - * the input document. - * - * The `class` attribute is special though because of the exposed helpers `add_class` - * and `remove_class`. These form a builder for the `class` attribute, so an additional - * check for enqueued class changes is required in addition to the check for any enqueued - * attribute values. If any exist, those enqueued class changes must first be flushed out - * into an attribute value update. - */ - if ( 'class' === $name ) { - $this->class_name_updates_to_attributes_updates(); - } - - // Return any enqueued attribute value updates if they exist. - $enqueued_value = $this->get_enqueued_attribute_value( $comparable ); - if ( false !== $enqueued_value ) { - return $enqueued_value; - } - - if ( ! isset( $this->attributes[ $comparable ] ) ) { - return null; - } - - $attribute = $this->attributes[ $comparable ]; - - /* - * This flag distinguishes an attribute with no value - * from an attribute with an empty string value. For - * unquoted attributes this could look very similar. - * It refers to whether an `=` follows the name. - * - * e.g.
- * ¹ ² - * 1. Attribute `boolean-attribute` is `true`. - * 2. Attribute `empty-attribute` is `""`. - */ - if ( true === $attribute->is_true ) { - return true; - } - - $raw_value = substr( $this->html, $attribute->value_starts_at, $attribute->value_length ); - - return Gutenberg_HTML_Decoder_6_6::decode_attribute( $raw_value ); - } - - /** - * Gets lowercase names of all attributes matching a given prefix in the current tag. - * - * Note that matching is case-insensitive. This is in accordance with the spec: - * - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag( array( 'class_name' => 'test' ) ) === true; - * $p->get_attribute_names_with_prefix( 'data-' ) === array( 'data-enabled', 'data-test-id' ); - * - * $p->next_tag() === false; - * $p->get_attribute_names_with_prefix( 'data-' ) === null; - * - * @since 6.2.0 - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - * - * @param string $prefix Prefix of requested attribute names. - * @return array|null List of attribute names, or `null` when no tag opener is matched. - */ - public function get_attribute_names_with_prefix( $prefix ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return null; - } - - $comparable = strtolower( $prefix ); - - $matches = array(); - foreach ( array_keys( $this->attributes ) as $attr_name ) { - if ( str_starts_with( $attr_name, $comparable ) ) { - $matches[] = $attr_name; - } - } - return $matches; - } - - /** - * Returns the uppercase name of the matched tag. - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
Test
' ); - * $p->next_tag() === true; - * $p->get_tag() === 'DIV'; - * - * $p->next_tag() === false; - * $p->get_tag() === null; - * - * @since 6.2.0 - * - * @return string|null Name of currently matched tag in input HTML, or `null` if none found. - */ - public function get_tag() { - if ( null === $this->tag_name_starts_at ) { - return null; - } - - $tag_name = substr( $this->html, $this->tag_name_starts_at, $this->tag_name_length ); - - if ( self::STATE_MATCHED_TAG === $this->parser_state ) { - return strtoupper( $tag_name ); - } - - if ( - self::STATE_COMMENT === $this->parser_state && - self::COMMENT_AS_PI_NODE_LOOKALIKE === $this->get_comment_type() - ) { - return $tag_name; - } - - return null; - } - - /** - * Indicates if the currently matched tag contains the self-closing flag. - * - * No HTML elements ought to have the self-closing flag and for those, the self-closing - * flag will be ignored. For void elements this is benign because they "self close" - * automatically. For non-void HTML elements though problems will appear if someone - * intends to use a self-closing element in place of that element with an empty body. - * For HTML foreign elements and custom elements the self-closing flag determines if - * they self-close or not. - * - * This function does not determine if a tag is self-closing, - * but only if the self-closing flag is present in the syntax. - * - * @since 6.3.0 - * - * @return bool Whether the currently matched tag contains the self-closing flag. - */ - public function has_self_closing_flag() { - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { - return false; - } - - /* - * The self-closing flag is the solidus at the _end_ of the tag, not the beginning. - * - * Example: - * - *
- * ^ this appears one character before the end of the closing ">". - */ - return '/' === $this->html[ $this->token_starts_at + $this->token_length - 2 ]; - } - - /** - * Indicates if the current tag token is a tag closer. - * - * Example: - * - * $p = new WP_HTML_Tag_Processor( '
' ); - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === false; - * - * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) ); - * $p->is_tag_closer() === true; - * - * @since 6.2.0 - * - * @return bool Whether the current tag is a tag closer. - */ - public function is_tag_closer() { - return ( - self::STATE_MATCHED_TAG === $this->parser_state && - $this->is_closing_tag - ); - } - - /** - * Indicates the kind of matched token, if any. - * - * This differs from `get_token_name()` in that it always - * returns a static string indicating the type, whereas - * `get_token_name()` may return values derived from the - * token itself, such as a tag name or processing - * instruction tag. - * - * Possible values: - * - `#tag` when matched on a tag. - * - `#text` when matched on a text node. - * - `#cdata-section` when matched on a CDATA node. - * - `#comment` when matched on a comment. - * - `#doctype` when matched on a DOCTYPE declaration. - * - `#presumptuous-tag` when matched on an empty tag closer. - * - `#funky-comment` when matched on a funky comment. - * - * @since 6.5.0 - * - * @return string|null What kind of token is matched, or null. - */ - public function get_token_type() { - switch ( $this->parser_state ) { - case self::STATE_MATCHED_TAG: - return '#tag'; - - case self::STATE_DOCTYPE: - return '#doctype'; - - default: - return $this->get_token_name(); - } - } - - /** - * Returns the node name represented by the token. - * - * This matches the DOM API value `nodeName`. Some values - * are static, such as `#text` for a text node, while others - * are dynamically generated from the token itself. - * - * Dynamic names: - * - Uppercase tag name for tag matches. - * - `html` for DOCTYPE declarations. - * - * Note that if the Tag Processor is not matched on a token - * then this function will return `null`, either because it - * hasn't yet found a token or because it reached the end - * of the document without matching a token. - * - * @since 6.5.0 - * - * @return string|null Name of the matched token. - */ - public function get_token_name() { - switch ( $this->parser_state ) { - case self::STATE_MATCHED_TAG: - return $this->get_tag(); - - case self::STATE_TEXT_NODE: - return '#text'; - - case self::STATE_CDATA_NODE: - return '#cdata-section'; - - case self::STATE_COMMENT: - return '#comment'; - - case self::STATE_DOCTYPE: - return 'html'; - - case self::STATE_PRESUMPTUOUS_TAG: - return '#presumptuous-tag'; - - case self::STATE_FUNKY_COMMENT: - return '#funky-comment'; - } - - return null; - } - - /** - * Indicates what kind of comment produced the comment node. - * - * Because there are different kinds of HTML syntax which produce - * comments, the Tag Processor tracks and exposes this as a type - * for the comment. Nominally only regular HTML comments exist as - * they are commonly known, but a number of unrelated syntax errors - * also produce comments. - * - * @see self::COMMENT_AS_ABRUPTLY_CLOSED_COMMENT - * @see self::COMMENT_AS_CDATA_LOOKALIKE - * @see self::COMMENT_AS_INVALID_HTML - * @see self::COMMENT_AS_HTML_COMMENT - * @see self::COMMENT_AS_PI_NODE_LOOKALIKE - * - * @since 6.5.0 - * - * @return string|null - */ - public function get_comment_type() { - if ( self::STATE_COMMENT !== $this->parser_state ) { - return null; - } - - return $this->comment_type; - } - - /** - * Returns the modifiable text for a matched token, or an empty string. - * - * Modifiable text is text content that may be read and changed without - * changing the HTML structure of the document around it. This includes - * the contents of `#text` nodes in the HTML as well as the inner - * contents of HTML comments, Processing Instructions, and others, even - * though these nodes aren't part of a parsed DOM tree. They also contain - * the contents of SCRIPT and STYLE tags, of TEXTAREA tags, and of any - * other section in an HTML document which cannot contain HTML markup (DATA). - * - * If a token has no modifiable text then an empty string is returned to - * avoid needless crashing or type errors. An empty string does not mean - * that a token has modifiable text, and a token with modifiable text may - * have an empty string (e.g. a comment with no contents). - * - * @since 6.5.0 - * - * @return string - */ - public function get_modifiable_text() { - if ( null === $this->text_starts_at ) { - return ''; - } - - $text = substr( $this->html, $this->text_starts_at, $this->text_length ); - - // Comment data is not decoded. - if ( - self::STATE_CDATA_NODE === $this->parser_state || - self::STATE_COMMENT === $this->parser_state || - self::STATE_DOCTYPE === $this->parser_state || - self::STATE_FUNKY_COMMENT === $this->parser_state - ) { - return $text; - } - - $tag_name = $this->get_tag(); - if ( - // Script data is not decoded. - 'SCRIPT' === $tag_name || - - // RAWTEXT data is not decoded. - 'IFRAME' === $tag_name || - 'NOEMBED' === $tag_name || - 'NOFRAMES' === $tag_name || - 'STYLE' === $tag_name || - 'XMP' === $tag_name - ) { - return $text; - } - - $decoded = Gutenberg_HTML_Decoder_6_6::decode_text_node( $text ); - - /* - * TEXTAREA skips a leading newline, but this newline may appear not only as the - * literal character `\n`, but also as a character reference, such as in the - * following markup: ``. - * - * For these cases it's important to first decode the text content before checking - * for a leading newline and removing it. - */ - if ( - self::STATE_MATCHED_TAG === $this->parser_state && - 'TEXTAREA' === $tag_name && - strlen( $decoded ) > 0 && - "\n" === $decoded[0] - ) { - return substr( $decoded, 1 ); - } - - return $decoded; - } - - /** - * Updates or creates a new attribute on the currently matched tag with the passed value. - * - * For boolean attributes special handling is provided: - * - When `true` is passed as the value, then only the attribute name is added to the tag. - * - When `false` is passed, the attribute gets removed if it existed before. - * - * For string attributes, the value is escaped using the `esc_attr` function. - * - * @since 6.2.0 - * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names. - * - * @param string $name The attribute name to target. - * @param string|bool $value The new attribute value. - * @return bool Whether an attribute value was set. - */ - public function set_attribute( $name, $value ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - /* - * WordPress rejects more characters than are strictly forbidden - * in HTML5. This is to prevent additional security risks deeper - * in the WordPress and plugin stack. Specifically the - * less-than (<) greater-than (>) and ampersand (&) aren't allowed. - * - * The use of a PCRE match enables looking for specific Unicode - * code points without writing a UTF-8 decoder. Whereas scanning - * for one-byte characters is trivial (with `strcspn`), scanning - * for the longer byte sequences would be more complicated. Given - * that this shouldn't be in the hot path for execution, it's a - * reasonable compromise in efficiency without introducing a - * noticeable impact on the overall system. - * - * @see https://html.spec.whatwg.org/#attributes-2 - * - * @todo As the only regex pattern maybe we should take it out? - * Are Unicode patterns available broadly in Core? - */ - if ( preg_match( - '~[' . - // Syntax-like characters. - '"\'>& The values "true" and "false" are not allowed on boolean attributes. - * > To represent a false value, the attribute has to be omitted altogether. - * - HTML5 spec, https://html.spec.whatwg.org/#boolean-attributes - */ - if ( false === $value ) { - return $this->remove_attribute( $name ); - } - - if ( true === $value ) { - $updated_attribute = $name; - } else { - $comparable_name = strtolower( $name ); - - /* - * Escape URL attributes. - * - * @see https://html.spec.whatwg.org/#attributes-3 - */ - $escaped_new_value = in_array( $comparable_name, wp_kses_uri_attributes() ) ? esc_url( $value ) : esc_attr( $value ); - $updated_attribute = "{$name}=\"{$escaped_new_value}\""; - } - - /* - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - */ - $comparable_name = strtolower( $name ); - - if ( isset( $this->attributes[ $comparable_name ] ) ) { - /* - * Update an existing attribute. - * - * Example – set attribute id to "new" in
: - * - *
- * ^-------------^ - * start end - * replacement: `id="new"` - * - * Result:
- */ - $existing_attribute = $this->attributes[ $comparable_name ]; - $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( - $existing_attribute->start, - $existing_attribute->length, - $updated_attribute - ); - } else { - /* - * Create a new attribute at the tag's name end. - * - * Example – add attribute id="new" to
: - * - *
- * ^ - * start and end - * replacement: ` id="new"` - * - * Result:
- */ - $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement( - $this->tag_name_starts_at + $this->tag_name_length, - 0, - ' ' . $updated_attribute - ); - } - - /* - * Any calls to update the `class` attribute directly should wipe out any - * enqueued class changes from `add_class` and `remove_class`. - */ - if ( 'class' === $comparable_name && ! empty( $this->classname_updates ) ) { - $this->classname_updates = array(); - } - - return true; - } - - /** - * Remove an attribute from the currently-matched tag. - * - * @since 6.2.0 - * - * @param string $name The attribute name to remove. - * @return bool Whether an attribute was removed. - */ - public function remove_attribute( $name ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - /* - * > There must never be two or more attributes on - * > the same start tag whose names are an ASCII - * > case-insensitive match for each other. - * - HTML 5 spec - * - * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive - */ - $name = strtolower( $name ); - - /* - * Any calls to update the `class` attribute directly should wipe out any - * enqueued class changes from `add_class` and `remove_class`. - */ - if ( 'class' === $name && count( $this->classname_updates ) !== 0 ) { - $this->classname_updates = array(); - } - - /* - * If updating an attribute that didn't exist in the input - * document, then remove the enqueued update and move on. - * - * For example, this might occur when calling `remove_attribute()` - * after calling `set_attribute()` for the same attribute - * and when that attribute wasn't originally present. - */ - if ( ! isset( $this->attributes[ $name ] ) ) { - if ( isset( $this->lexical_updates[ $name ] ) ) { - unset( $this->lexical_updates[ $name ] ); - } - return false; - } - - /* - * Removes an existing tag attribute. - * - * Example – remove the attribute id from
: - *
- * ^-------------^ - * start end - * replacement: `` - * - * Result:
- */ - $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement( - $this->attributes[ $name ]->start, - $this->attributes[ $name ]->length, - '' - ); - - // Removes any duplicated attributes if they were also present. - if ( null !== $this->duplicate_attributes && array_key_exists( $name, $this->duplicate_attributes ) ) { - foreach ( $this->duplicate_attributes[ $name ] as $attribute_token ) { - $this->lexical_updates[] = new WP_HTML_Text_Replacement( - $attribute_token->start, - $attribute_token->length, - '' - ); - } - } - - return true; - } - - /** - * Adds a new class name to the currently matched tag. - * - * @since 6.2.0 - * - * @param string $class_name The class name to add. - * @return bool Whether the class was set to be added. - */ - public function add_class( $class_name ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - $this->classname_updates[ $class_name ] = self::ADD_CLASS; - - return true; - } - - /** - * Removes a class name from the currently matched tag. - * - * @since 6.2.0 - * - * @param string $class_name The class name to remove. - * @return bool Whether the class was set to be removed. - */ - public function remove_class( $class_name ) { - if ( - self::STATE_MATCHED_TAG !== $this->parser_state || - $this->is_closing_tag - ) { - return false; - } - - if ( null !== $this->tag_name_starts_at ) { - $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; - } - - return true; - } - - /** - * Returns the string representation of the HTML Tag Processor. - * - * @since 6.2.0 - * - * @see WP_HTML_Tag_Processor::get_updated_html() - * - * @return string The processed HTML. - */ - public function __toString() { - return $this->get_updated_html(); - } - - /** - * Returns the string representation of the HTML Tag Processor. - * - * @since 6.2.0 - * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates. - * @since 6.4.0 No longer calls subclass method `next_tag()` after updating HTML. - * - * @return string The processed HTML. - */ - public function get_updated_html() { - $requires_no_updating = 0 === count( $this->classname_updates ) && 0 === count( $this->lexical_updates ); - - /* - * When there is nothing more to update and nothing has already been - * updated, return the original document and avoid a string copy. - */ - if ( $requires_no_updating ) { - return $this->html; - } - - /* - * Keep track of the position right before the current tag. This will - * be necessary for reparsing the current tag after updating the HTML. - */ - $before_current_tag = $this->token_starts_at ?? 0; - - /* - * 1. Apply the enqueued edits and update all the pointers to reflect those changes. - */ - $this->class_name_updates_to_attributes_updates(); - $before_current_tag += $this->apply_attributes_updates( $before_current_tag ); - - /* - * 2. Rewind to before the current tag and reparse to get updated attributes. - * - * At this point the internal cursor points to the end of the tag name. - * Rewind before the tag name starts so that it's as if the cursor didn't - * move; a call to `next_tag()` will reparse the recently-updated attributes - * and additional calls to modify the attributes will apply at this same - * location, but in order to avoid issues with subclasses that might add - * behaviors to `next_tag()`, the internal methods should be called here - * instead. - * - * It's important to note that in this specific place there will be no change - * because the processor was already at a tag when this was called and it's - * rewinding only to the beginning of this very tag before reprocessing it - * and its attributes. - * - *

Previous HTMLMore HTML

- * ↑ │ back up by the length of the tag name plus the opening < - * └←─┘ back up by strlen("em") + 1 ==> 3 - */ - $this->bytes_already_parsed = $before_current_tag; - $this->base_class_next_token(); - - return $this->html; - } - - /** - * Parses tag query input into internal search criteria. - * - * @since 6.2.0 - * - * @param array|string|null $query { - * Optional. Which tag name to find, having which class, etc. Default is to find any tag. - * - * @type string|null $tag_name Which tag to find, or `null` for "any tag." - * @type int|null $match_offset Find the Nth tag matching all search criteria. - * 1 for "first" tag, 3 for "third," etc. - * Defaults to first tag. - * @type string|null $class_name Tag must contain this class name to match. - * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
. - * } - */ - private function parse_query( $query ) { - if ( null !== $query && $query === $this->last_query ) { - return; - } - - $this->last_query = $query; - $this->sought_tag_name = null; - $this->sought_class_name = null; - $this->sought_match_offset = 1; - $this->stop_on_tag_closers = false; - - // A single string value means "find the tag of this name". - if ( is_string( $query ) ) { - $this->sought_tag_name = $query; - return; - } - - // An empty query parameter applies no restrictions on the search. - if ( null === $query ) { - return; - } - - // If not using the string interface, an associative array is required. - if ( ! is_array( $query ) ) { - _doing_it_wrong( - __METHOD__, - __( 'The query argument must be an array or a tag name.' ), - '6.2.0' - ); - return; - } - - if ( isset( $query['tag_name'] ) && is_string( $query['tag_name'] ) ) { - $this->sought_tag_name = $query['tag_name']; - } - - if ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) { - $this->sought_class_name = $query['class_name']; - } - - if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) { - $this->sought_match_offset = $query['match_offset']; - } - - if ( isset( $query['tag_closers'] ) ) { - $this->stop_on_tag_closers = 'visit' === $query['tag_closers']; - } - } - - - /** - * Checks whether a given tag and its attributes match the search criteria. - * - * @since 6.2.0 - * - * @return bool Whether the given tag and its attribute match the search criteria. - */ - private function matches() { - if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) { - return false; - } - - // Does the tag name match the requested tag name in a case-insensitive manner? - if ( null !== $this->sought_tag_name ) { - /* - * String (byte) length lookup is fast. If they aren't the - * same length then they can't be the same string values. - */ - if ( strlen( $this->sought_tag_name ) !== $this->tag_name_length ) { - return false; - } - - /* - * Check each character to determine if they are the same. - * Defer calls to `strtoupper()` to avoid them when possible. - * Calling `strcasecmp()` here tested slowed than comparing each - * character, so unless benchmarks show otherwise, it should - * not be used. - * - * It's expected that most of the time that this runs, a - * lower-case tag name will be supplied and the input will - * contain lower-case tag names, thus normally bypassing - * the case comparison code. - */ - for ( $i = 0; $i < $this->tag_name_length; $i++ ) { - $html_char = $this->html[ $this->tag_name_starts_at + $i ]; - $tag_char = $this->sought_tag_name[ $i ]; - - if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) { - return false; - } - } - } - - if ( null !== $this->sought_class_name && ! $this->has_class( $this->sought_class_name ) ) { - return false; - } - - return true; - } - - /** - * Parser Ready State. - * - * Indicates that the parser is ready to run and waiting for a state transition. - * It may not have started yet, or it may have just finished parsing a token and - * is ready to find the next one. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_READY = 'STATE_READY'; - - /** - * Parser Complete State. - * - * Indicates that the parser has reached the end of the document and there is - * nothing left to scan. It finished parsing the last token completely. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_COMPLETE = 'STATE_COMPLETE'; - - /** - * Parser Incomplete Input State. - * - * Indicates that the parser has reached the end of the document before finishing - * a token. It started parsing a token but there is a possibility that the input - * HTML document was truncated in the middle of a token. - * - * The parser is reset at the start of the incomplete token and has paused. There - * is nothing more than can be scanned unless provided a more complete document. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_INCOMPLETE_INPUT = 'STATE_INCOMPLETE_INPUT'; - - /** - * Parser Matched Tag State. - * - * Indicates that the parser has found an HTML tag and it's possible to get - * the tag name and read or modify its attributes (if it's not a closing tag). - * - * @since 6.5.0 - * - * @access private - */ - const STATE_MATCHED_TAG = 'STATE_MATCHED_TAG'; - - /** - * Parser Text Node State. - * - * Indicates that the parser has found a text node and it's possible - * to read and modify that text. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_TEXT_NODE = 'STATE_TEXT_NODE'; - - /** - * Parser CDATA Node State. - * - * Indicates that the parser has found a CDATA node and it's possible - * to read and modify its modifiable text. Note that in HTML there are - * no CDATA nodes outside of foreign content (SVG and MathML). Outside - * of foreign content, they are treated as HTML comments. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_CDATA_NODE = 'STATE_CDATA_NODE'; - - /** - * Indicates that the parser has found an HTML comment and it's - * possible to read and modify its modifiable text. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_COMMENT = 'STATE_COMMENT'; - - /** - * Indicates that the parser has found a DOCTYPE node and it's - * possible to read and modify its modifiable text. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_DOCTYPE = 'STATE_DOCTYPE'; - - /** - * Indicates that the parser has found an empty tag closer ``. - * - * Note that in HTML there are no empty tag closers, and they - * are ignored. Nonetheless, the Tag Processor still - * recognizes them as they appear in the HTML stream. - * - * These were historically discussed as a "presumptuous tag - * closer," which would close the nearest open tag, but were - * dismissed in favor of explicitly-closing tags. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_PRESUMPTUOUS_TAG = 'STATE_PRESUMPTUOUS_TAG'; - - /** - * Indicates that the parser has found a "funky comment" - * and it's possible to read and modify its modifiable text. - * - * Example: - * - * - * - * - * - * Funky comments are tag closers with invalid tag names. Note - * that in HTML these are turn into bogus comments. Nonetheless, - * the Tag Processor recognizes them in a stream of HTML and - * exposes them for inspection and modification. - * - * @since 6.5.0 - * - * @access private - */ - const STATE_FUNKY_COMMENT = 'STATE_WP_FUNKY'; - - /** - * Indicates that a comment was created when encountering abruptly-closed HTML comment. - * - * Example: - * - * - * - * - * @since 6.5.0 - */ - const COMMENT_AS_ABRUPTLY_CLOSED_COMMENT = 'COMMENT_AS_ABRUPTLY_CLOSED_COMMENT'; - - /** - * Indicates that a comment would be parsed as a CDATA node, - * were HTML to allow CDATA nodes outside of foreign content. - * - * Example: - * - * - * - * This is an HTML comment, but it looks like a CDATA node. - * - * @since 6.5.0 - */ - const COMMENT_AS_CDATA_LOOKALIKE = 'COMMENT_AS_CDATA_LOOKALIKE'; - - /** - * Indicates that a comment was created when encountering - * normative HTML comment syntax. - * - * Example: - * - * - * - * @since 6.5.0 - */ - const COMMENT_AS_HTML_COMMENT = 'COMMENT_AS_HTML_COMMENT'; - - /** - * Indicates that a comment would be parsed as a Processing - * Instruction node, were they to exist within HTML. - * - * Example: - * - * - * - * This is an HTML comment, but it looks like a CDATA node. - * - * @since 6.5.0 - */ - const COMMENT_AS_PI_NODE_LOOKALIKE = 'COMMENT_AS_PI_NODE_LOOKALIKE'; - - /** - * Indicates that a comment was created when encountering invalid - * HTML input, a so-called "bogus comment." - * - * Example: - * - * - * - * - * @since 6.5.0 - */ - const COMMENT_AS_INVALID_HTML = 'COMMENT_AS_INVALID_HTML'; -} diff --git a/lib/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php b/lib/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php deleted file mode 100644 index c09deb3c9e6642..00000000000000 --- a/lib/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php +++ /dev/null @@ -1,1315 +0,0 @@ - "6.6.0-trunk", - "key_length" => 2, - "groups" => "AE\x00AM\x00Aa\x00Ab\x00Ac\x00Af\x00Ag\x00Al\x00Am\x00An\x00Ao\x00Ap\x00Ar\x00As\x00At\x00Au\x00Ba\x00Bc\x00Be\x00Bf\x00Bo\x00Br\x00Bs\x00Bu\x00CH\x00CO\x00Ca\x00Cc\x00Cd\x00Ce\x00Cf\x00Ch\x00Ci\x00Cl\x00Co\x00Cr\x00Cs\x00Cu\x00DD\x00DJ\x00DS\x00DZ\x00Da\x00Dc\x00De\x00Df\x00Di\x00Do\x00Ds\x00EN\x00ET\x00Ea\x00Ec\x00Ed\x00Ef\x00Eg\x00El\x00Em\x00Eo\x00Ep\x00Eq\x00Es\x00Et\x00Eu\x00Ex\x00Fc\x00Ff\x00Fi\x00Fo\x00Fs\x00GJ\x00GT\x00Ga\x00Gb\x00Gc\x00Gd\x00Gf\x00Gg\x00Go\x00Gr\x00Gs\x00Gt\x00HA\x00Ha\x00Hc\x00Hf\x00Hi\x00Ho\x00Hs\x00Hu\x00IE\x00IJ\x00IO\x00Ia\x00Ic\x00Id\x00If\x00Ig\x00Im\x00In\x00Io\x00Is\x00It\x00Iu\x00Jc\x00Jf\x00Jo\x00Js\x00Ju\x00KH\x00KJ\x00Ka\x00Kc\x00Kf\x00Ko\x00Ks\x00LJ\x00LT\x00La\x00Lc\x00Le\x00Lf\x00Ll\x00Lm\x00Lo\x00Ls\x00Lt\x00Ma\x00Mc\x00Me\x00Mf\x00Mi\x00Mo\x00Ms\x00Mu\x00NJ\x00Na\x00Nc\x00Ne\x00Nf\x00No\x00Ns\x00Nt\x00Nu\x00OE\x00Oa\x00Oc\x00Od\x00Of\x00Og\x00Om\x00Oo\x00Op\x00Or\x00Os\x00Ot\x00Ou\x00Ov\x00Pa\x00Pc\x00Pf\x00Ph\x00Pi\x00Pl\x00Po\x00Pr\x00Ps\x00QU\x00Qf\x00Qo\x00Qs\x00RB\x00RE\x00Ra\x00Rc\x00Re\x00Rf\x00Rh\x00Ri\x00Ro\x00Rr\x00Rs\x00Ru\x00SH\x00SO\x00Sa\x00Sc\x00Sf\x00Sh\x00Si\x00Sm\x00So\x00Sq\x00Ss\x00St\x00Su\x00TH\x00TR\x00TS\x00Ta\x00Tc\x00Tf\x00Th\x00Ti\x00To\x00Tr\x00Ts\x00Ua\x00Ub\x00Uc\x00Ud\x00Uf\x00Ug\x00Um\x00Un\x00Uo\x00Up\x00Ur\x00Us\x00Ut\x00Uu\x00VD\x00Vb\x00Vc\x00Vd\x00Ve\x00Vf\x00Vo\x00Vs\x00Vv\x00Wc\x00We\x00Wf\x00Wo\x00Ws\x00Xf\x00Xi\x00Xo\x00Xs\x00YA\x00YI\x00YU\x00Ya\x00Yc\x00Yf\x00Yo\x00Ys\x00Yu\x00ZH\x00Za\x00Zc\x00Zd\x00Ze\x00Zf\x00Zo\x00Zs\x00aa\x00ab\x00ac\x00ae\x00af\x00ag\x00al\x00am\x00an\x00ao\x00ap\x00ar\x00as\x00at\x00au\x00aw\x00bN\x00ba\x00bb\x00bc\x00bd\x00be\x00bf\x00bi\x00bk\x00bl\x00bn\x00bo\x00bp\x00br\x00bs\x00bu\x00ca\x00cc\x00cd\x00ce\x00cf\x00ch\x00ci\x00cl\x00co\x00cr\x00cs\x00ct\x00cu\x00cw\x00cy\x00dA\x00dH\x00da\x00db\x00dc\x00dd\x00de\x00df\x00dh\x00di\x00dj\x00dl\x00do\x00dr\x00ds\x00dt\x00du\x00dw\x00dz\x00eD\x00ea\x00ec\x00ed\x00ee\x00ef\x00eg\x00el\x00em\x00en\x00eo\x00ep\x00eq\x00er\x00es\x00et\x00eu\x00ex\x00fa\x00fc\x00fe\x00ff\x00fi\x00fj\x00fl\x00fn\x00fo\x00fp\x00fr\x00fs\x00gE\x00ga\x00gb\x00gc\x00gd\x00ge\x00gf\x00gg\x00gi\x00gj\x00gl\x00gn\x00go\x00gr\x00gs\x00gt\x00gv\x00hA\x00ha\x00hb\x00hc\x00he\x00hf\x00hk\x00ho\x00hs\x00hy\x00ia\x00ic\x00ie\x00if\x00ig\x00ii\x00ij\x00im\x00in\x00io\x00ip\x00iq\x00is\x00it\x00iu\x00jc\x00jf\x00jm\x00jo\x00js\x00ju\x00ka\x00kc\x00kf\x00kg\x00kh\x00kj\x00ko\x00ks\x00lA\x00lB\x00lE\x00lH\x00la\x00lb\x00lc\x00ld\x00le\x00lf\x00lg\x00lh\x00lj\x00ll\x00lm\x00ln\x00lo\x00lp\x00lr\x00ls\x00lt\x00lu\x00lv\x00mD\x00ma\x00mc\x00md\x00me\x00mf\x00mh\x00mi\x00ml\x00mn\x00mo\x00mp\x00ms\x00mu\x00nG\x00nL\x00nR\x00nV\x00na\x00nb\x00nc\x00nd\x00ne\x00nf\x00ng\x00nh\x00ni\x00nj\x00nl\x00nm\x00no\x00np\x00nr\x00ns\x00nt\x00nu\x00nv\x00nw\x00oS\x00oa\x00oc\x00od\x00oe\x00of\x00og\x00oh\x00oi\x00ol\x00om\x00oo\x00op\x00or\x00os\x00ot\x00ou\x00ov\x00pa\x00pc\x00pe\x00pf\x00ph\x00pi\x00pl\x00pm\x00po\x00pr\x00ps\x00pu\x00qf\x00qi\x00qo\x00qp\x00qs\x00qu\x00rA\x00rB\x00rH\x00ra\x00rb\x00rc\x00rd\x00re\x00rf\x00rh\x00ri\x00rl\x00rm\x00rn\x00ro\x00rp\x00rr\x00rs\x00rt\x00ru\x00rx\x00sa\x00sb\x00sc\x00sd\x00se\x00sf\x00sh\x00si\x00sl\x00sm\x00so\x00sp\x00sq\x00sr\x00ss\x00st\x00su\x00sw\x00sz\x00ta\x00tb\x00tc\x00td\x00te\x00tf\x00th\x00ti\x00to\x00tp\x00tr\x00ts\x00tw\x00uA\x00uH\x00ua\x00ub\x00uc\x00ud\x00uf\x00ug\x00uh\x00ul\x00um\x00uo\x00up\x00ur\x00us\x00ut\x00uu\x00uw\x00vA\x00vB\x00vD\x00va\x00vc\x00vd\x00ve\x00vf\x00vl\x00vn\x00vo\x00vp\x00vr\x00vs\x00vz\x00wc\x00we\x00wf\x00wo\x00wp\x00wr\x00ws\x00xc\x00xd\x00xf\x00xh\x00xi\x00xl\x00xm\x00xn\x00xo\x00xr\x00xs\x00xu\x00xv\x00xw\x00ya\x00yc\x00ye\x00yf\x00yi\x00yo\x00ys\x00yu\x00za\x00zc\x00zd\x00ze\x00zf\x00zh\x00zi\x00zo\x00zs\x00zw\x00", - "large_words" => array( - // AElig;[Æ] AElig[Æ]. - "\x04lig;\x02Æ\x03lig\x02Æ", - // AMP;[&] AMP[&]. - "\x02P;\x01&\x01P\x01&", - // Aacute;[Á] Aacute[Á]. - "\x05cute;\x02Á\x04cute\x02Á", - // Abreve;[Ă]. - "\x05reve;\x02Ă", - // Acirc;[Â] Acirc[Â] Acy;[А]. - "\x04irc;\x02Â\x03irc\x02Â\x02y;\x02А", - // Afr;[𝔄]. - "\x02r;\x04𝔄", - // Agrave;[À] Agrave[À]. - "\x05rave;\x02À\x04rave\x02À", - // Alpha;[Α]. - "\x04pha;\x02Α", - // Amacr;[Ā]. - "\x04acr;\x02Ā", - // And;[⩓]. - "\x02d;\x03⩓", - // Aogon;[Ą] Aopf;[𝔸]. - "\x04gon;\x02Ą\x03pf;\x04𝔸", - // ApplyFunction;[⁡]. - "\x0cplyFunction;\x03⁡", - // Aring;[Å] Aring[Å]. - "\x04ing;\x02Å\x03ing\x02Å", - // Assign;[≔] Ascr;[𝒜]. - "\x05sign;\x03≔\x03cr;\x04𝒜", - // Atilde;[Ã] Atilde[Ã]. - "\x05ilde;\x02Ã\x04ilde\x02Ã", - // Auml;[Ä] Auml[Ä]. - "\x03ml;\x02Ä\x02ml\x02Ä", - // Backslash;[∖] Barwed;[⌆] Barv;[⫧]. - "\x08ckslash;\x03∖\x05rwed;\x03⌆\x03rv;\x03⫧", - // Bcy;[Б]. - "\x02y;\x02Б", - // Bernoullis;[ℬ] Because;[∵] Beta;[Β]. - "\x09rnoullis;\x03ℬ\x06cause;\x03∵\x03ta;\x02Β", - // Bfr;[𝔅]. - "\x02r;\x04𝔅", - // Bopf;[𝔹]. - "\x03pf;\x04𝔹", - // Breve;[˘]. - "\x04eve;\x02˘", - // Bscr;[ℬ]. - "\x03cr;\x03ℬ", - // Bumpeq;[≎]. - "\x05mpeq;\x03≎", - // CHcy;[Ч]. - "\x03cy;\x02Ч", - // COPY;[©] COPY[©]. - "\x03PY;\x02©\x02PY\x02©", - // CapitalDifferentialD;[ⅅ] Cayleys;[ℭ] Cacute;[Ć] Cap;[⋒]. - "\x13pitalDifferentialD;\x03ⅅ\x06yleys;\x03ℭ\x05cute;\x02Ć\x02p;\x03⋒", - // Cconint;[∰] Ccaron;[Č] Ccedil;[Ç] Ccedil[Ç] Ccirc;[Ĉ]. - "\x06onint;\x03∰\x05aron;\x02Č\x05edil;\x02Ç\x04edil\x02Ç\x04irc;\x02Ĉ", - // Cdot;[Ċ]. - "\x03ot;\x02Ċ", - // CenterDot;[·] Cedilla;[¸]. - "\x08nterDot;\x02·\x06dilla;\x02¸", - // Cfr;[ℭ]. - "\x02r;\x03ℭ", - // Chi;[Χ]. - "\x02i;\x02Χ", - // CircleMinus;[⊖] CircleTimes;[⊗] CirclePlus;[⊕] CircleDot;[⊙]. - "\x0arcleMinus;\x03⊖\x0arcleTimes;\x03⊗\x09rclePlus;\x03⊕\x08rcleDot;\x03⊙", - // ClockwiseContourIntegral;[∲] CloseCurlyDoubleQuote;[”] CloseCurlyQuote;[’]. - "\x17ockwiseContourIntegral;\x03∲\x14oseCurlyDoubleQuote;\x03”\x0eoseCurlyQuote;\x03’", - // CounterClockwiseContourIntegral;[∳] ContourIntegral;[∮] Congruent;[≡] Coproduct;[∐] Colone;[⩴] Conint;[∯] Colon;[∷] Copf;[ℂ]. - "\x1eunterClockwiseContourIntegral;\x03∳\x0entourIntegral;\x03∮\x08ngruent;\x03≡\x08product;\x03∐\x05lone;\x03⩴\x05nint;\x03∯\x04lon;\x03∷\x03pf;\x03ℂ", - // Cross;[⨯]. - "\x04oss;\x03⨯", - // Cscr;[𝒞]. - "\x03cr;\x04𝒞", - // CupCap;[≍] Cup;[⋓]. - "\x05pCap;\x03≍\x02p;\x03⋓", - // DDotrahd;[⤑] DD;[ⅅ]. - "\x07otrahd;\x03⤑\x01;\x03ⅅ", - // DJcy;[Ђ]. - "\x03cy;\x02Ђ", - // DScy;[Ѕ]. - "\x03cy;\x02Ѕ", - // DZcy;[Џ]. - "\x03cy;\x02Џ", - // Dagger;[‡] Dashv;[⫤] Darr;[↡]. - "\x05gger;\x03‡\x04shv;\x03⫤\x03rr;\x03↡", - // Dcaron;[Ď] Dcy;[Д]. - "\x05aron;\x02Ď\x02y;\x02Д", - // Delta;[Δ] Del;[∇]. - "\x04lta;\x02Δ\x02l;\x03∇", - // Dfr;[𝔇]. - "\x02r;\x04𝔇", - // DiacriticalDoubleAcute;[˝] DiacriticalAcute;[´] DiacriticalGrave;[`] DiacriticalTilde;[˜] DiacriticalDot;[˙] DifferentialD;[ⅆ] Diamond;[⋄]. - "\x15acriticalDoubleAcute;\x02˝\x0facriticalAcute;\x02´\x0facriticalGrave;\x01`\x0facriticalTilde;\x02˜\x0dacriticalDot;\x02˙\x0cfferentialD;\x03ⅆ\x06amond;\x03⋄", - // DoubleLongLeftRightArrow;[⟺] DoubleContourIntegral;[∯] DoubleLeftRightArrow;[⇔] DoubleLongRightArrow;[⟹] DoubleLongLeftArrow;[⟸] DownLeftRightVector;[⥐] DownRightTeeVector;[⥟] DownRightVectorBar;[⥗] DoubleUpDownArrow;[⇕] DoubleVerticalBar;[∥] DownLeftTeeVector;[⥞] DownLeftVectorBar;[⥖] DoubleRightArrow;[⇒] DownArrowUpArrow;[⇵] DoubleDownArrow;[⇓] DoubleLeftArrow;[⇐] DownRightVector;[⇁] DoubleRightTee;[⊨] DownLeftVector;[↽] DoubleLeftTee;[⫤] DoubleUpArrow;[⇑] DownArrowBar;[⤓] DownTeeArrow;[↧] DoubleDot;[¨] DownArrow;[↓] DownBreve;[̑] Downarrow;[⇓] DotEqual;[≐] DownTee;[⊤] DotDot;[⃜] Dopf;[𝔻] Dot;[¨]. - "\x17ubleLongLeftRightArrow;\x03⟺\x14ubleContourIntegral;\x03∯\x13ubleLeftRightArrow;\x03⇔\x13ubleLongRightArrow;\x03⟹\x12ubleLongLeftArrow;\x03⟸\x12wnLeftRightVector;\x03⥐\x11wnRightTeeVector;\x03⥟\x11wnRightVectorBar;\x03⥗\x10ubleUpDownArrow;\x03⇕\x10ubleVerticalBar;\x03∥\x10wnLeftTeeVector;\x03⥞\x10wnLeftVectorBar;\x03⥖\x0fubleRightArrow;\x03⇒\x0fwnArrowUpArrow;\x03⇵\x0eubleDownArrow;\x03⇓\x0eubleLeftArrow;\x03⇐\x0ewnRightVector;\x03⇁\x0dubleRightTee;\x03⊨\x0dwnLeftVector;\x03↽\x0cubleLeftTee;\x03⫤\x0cubleUpArrow;\x03⇑\x0bwnArrowBar;\x03⤓\x0bwnTeeArrow;\x03↧\x08ubleDot;\x02¨\x08wnArrow;\x03↓\x08wnBreve;\x02̑\x08wnarrow;\x03⇓\x07tEqual;\x03≐\x06wnTee;\x03⊤\x05tDot;\x03⃜\x03pf;\x04𝔻\x02t;\x02¨", - // Dstrok;[Đ] Dscr;[𝒟]. - "\x05trok;\x02Đ\x03cr;\x04𝒟", - // ENG;[Ŋ]. - "\x02G;\x02Ŋ", - // ETH;[Ð] ETH[Ð]. - "\x02H;\x02Ð\x01H\x02Ð", - // Eacute;[É] Eacute[É]. - "\x05cute;\x02É\x04cute\x02É", - // Ecaron;[Ě] Ecirc;[Ê] Ecirc[Ê] Ecy;[Э]. - "\x05aron;\x02Ě\x04irc;\x02Ê\x03irc\x02Ê\x02y;\x02Э", - // Edot;[Ė]. - "\x03ot;\x02Ė", - // Efr;[𝔈]. - "\x02r;\x04𝔈", - // Egrave;[È] Egrave[È]. - "\x05rave;\x02È\x04rave\x02È", - // Element;[∈]. - "\x06ement;\x03∈", - // EmptyVerySmallSquare;[▫] EmptySmallSquare;[◻] Emacr;[Ē]. - "\x13ptyVerySmallSquare;\x03▫\x0fptySmallSquare;\x03◻\x04acr;\x02Ē", - // Eogon;[Ę] Eopf;[𝔼]. - "\x04gon;\x02Ę\x03pf;\x04𝔼", - // Epsilon;[Ε]. - "\x06silon;\x02Ε", - // Equilibrium;[⇌] EqualTilde;[≂] Equal;[⩵]. - "\x0auilibrium;\x03⇌\x09ualTilde;\x03≂\x04ual;\x03⩵", - // Escr;[ℰ] Esim;[⩳]. - "\x03cr;\x03ℰ\x03im;\x03⩳", - // Eta;[Η]. - "\x02a;\x02Η", - // Euml;[Ë] Euml[Ë]. - "\x03ml;\x02Ë\x02ml\x02Ë", - // ExponentialE;[ⅇ] Exists;[∃]. - "\x0bponentialE;\x03ⅇ\x05ists;\x03∃", - // Fcy;[Ф]. - "\x02y;\x02Ф", - // Ffr;[𝔉]. - "\x02r;\x04𝔉", - // FilledVerySmallSquare;[▪] FilledSmallSquare;[◼]. - "\x14lledVerySmallSquare;\x03▪\x10lledSmallSquare;\x03◼", - // Fouriertrf;[ℱ] ForAll;[∀] Fopf;[𝔽]. - "\x09uriertrf;\x03ℱ\x05rAll;\x03∀\x03pf;\x04𝔽", - // Fscr;[ℱ]. - "\x03cr;\x03ℱ", - // GJcy;[Ѓ]. - "\x03cy;\x02Ѓ", - // GT;[>]. - "\x01;\x01>", - // Gammad;[Ϝ] Gamma;[Γ]. - "\x05mmad;\x02Ϝ\x04mma;\x02Γ", - // Gbreve;[Ğ]. - "\x05reve;\x02Ğ", - // Gcedil;[Ģ] Gcirc;[Ĝ] Gcy;[Г]. - "\x05edil;\x02Ģ\x04irc;\x02Ĝ\x02y;\x02Г", - // Gdot;[Ġ]. - "\x03ot;\x02Ġ", - // Gfr;[𝔊]. - "\x02r;\x04𝔊", - // Gg;[⋙]. - "\x01;\x03⋙", - // Gopf;[𝔾]. - "\x03pf;\x04𝔾", - // GreaterSlantEqual;[⩾] GreaterEqualLess;[⋛] GreaterFullEqual;[≧] GreaterGreater;[⪢] GreaterEqual;[≥] GreaterTilde;[≳] GreaterLess;[≷]. - "\x10eaterSlantEqual;\x03⩾\x0featerEqualLess;\x03⋛\x0featerFullEqual;\x03≧\x0deaterGreater;\x03⪢\x0beaterEqual;\x03≥\x0beaterTilde;\x03≳\x0aeaterLess;\x03≷", - // Gscr;[𝒢]. - "\x03cr;\x04𝒢", - // Gt;[≫]. - "\x01;\x03≫", - // HARDcy;[Ъ]. - "\x05RDcy;\x02Ъ", - // Hacek;[ˇ] Hat;[^]. - "\x04cek;\x02ˇ\x02t;\x01^", - // Hcirc;[Ĥ]. - "\x04irc;\x02Ĥ", - // Hfr;[ℌ]. - "\x02r;\x03ℌ", - // HilbertSpace;[ℋ]. - "\x0blbertSpace;\x03ℋ", - // HorizontalLine;[─] Hopf;[ℍ]. - "\x0drizontalLine;\x03─\x03pf;\x03ℍ", - // Hstrok;[Ħ] Hscr;[ℋ]. - "\x05trok;\x02Ħ\x03cr;\x03ℋ", - // HumpDownHump;[≎] HumpEqual;[≏]. - "\x0bmpDownHump;\x03≎\x08mpEqual;\x03≏", - // IEcy;[Е]. - "\x03cy;\x02Е", - // IJlig;[IJ]. - "\x04lig;\x02IJ", - // IOcy;[Ё]. - "\x03cy;\x02Ё", - // Iacute;[Í] Iacute[Í]. - "\x05cute;\x02Í\x04cute\x02Í", - // Icirc;[Î] Icirc[Î] Icy;[И]. - "\x04irc;\x02Î\x03irc\x02Î\x02y;\x02И", - // Idot;[İ]. - "\x03ot;\x02İ", - // Ifr;[ℑ]. - "\x02r;\x03ℑ", - // Igrave;[Ì] Igrave[Ì]. - "\x05rave;\x02Ì\x04rave\x02Ì", - // ImaginaryI;[ⅈ] Implies;[⇒] Imacr;[Ī] Im;[ℑ]. - "\x09aginaryI;\x03ⅈ\x06plies;\x03⇒\x04acr;\x02Ī\x01;\x03ℑ", - // InvisibleComma;[⁣] InvisibleTimes;[⁢] Intersection;[⋂] Integral;[∫] Int;[∬]. - "\x0dvisibleComma;\x03⁣\x0dvisibleTimes;\x03⁢\x0btersection;\x03⋂\x07tegral;\x03∫\x02t;\x03∬", - // Iogon;[Į] Iopf;[𝕀] Iota;[Ι]. - "\x04gon;\x02Į\x03pf;\x04𝕀\x03ta;\x02Ι", - // Iscr;[ℐ]. - "\x03cr;\x03ℐ", - // Itilde;[Ĩ]. - "\x05ilde;\x02Ĩ", - // Iukcy;[І] Iuml;[Ï] Iuml[Ï]. - "\x04kcy;\x02І\x03ml;\x02Ï\x02ml\x02Ï", - // Jcirc;[Ĵ] Jcy;[Й]. - "\x04irc;\x02Ĵ\x02y;\x02Й", - // Jfr;[𝔍]. - "\x02r;\x04𝔍", - // Jopf;[𝕁]. - "\x03pf;\x04𝕁", - // Jsercy;[Ј] Jscr;[𝒥]. - "\x05ercy;\x02Ј\x03cr;\x04𝒥", - // Jukcy;[Є]. - "\x04kcy;\x02Є", - // KHcy;[Х]. - "\x03cy;\x02Х", - // KJcy;[Ќ]. - "\x03cy;\x02Ќ", - // Kappa;[Κ]. - "\x04ppa;\x02Κ", - // Kcedil;[Ķ] Kcy;[К]. - "\x05edil;\x02Ķ\x02y;\x02К", - // Kfr;[𝔎]. - "\x02r;\x04𝔎", - // Kopf;[𝕂]. - "\x03pf;\x04𝕂", - // Kscr;[𝒦]. - "\x03cr;\x04𝒦", - // LJcy;[Љ]. - "\x03cy;\x02Љ", - // LT;[<]. - "\x01;\x01<", - // Laplacetrf;[ℒ] Lacute;[Ĺ] Lambda;[Λ] Lang;[⟪] Larr;[↞]. - "\x09placetrf;\x03ℒ\x05cute;\x02Ĺ\x05mbda;\x02Λ\x03ng;\x03⟪\x03rr;\x03↞", - // Lcaron;[Ľ] Lcedil;[Ļ] Lcy;[Л]. - "\x05aron;\x02Ľ\x05edil;\x02Ļ\x02y;\x02Л", - // LeftArrowRightArrow;[⇆] LeftDoubleBracket;[⟦] LeftDownTeeVector;[⥡] LeftDownVectorBar;[⥙] LeftTriangleEqual;[⊴] LeftAngleBracket;[⟨] LeftUpDownVector;[⥑] LessEqualGreater;[⋚] LeftRightVector;[⥎] LeftTriangleBar;[⧏] LeftUpTeeVector;[⥠] LeftUpVectorBar;[⥘] LeftDownVector;[⇃] LeftRightArrow;[↔] Leftrightarrow;[⇔] LessSlantEqual;[⩽] LeftTeeVector;[⥚] LeftVectorBar;[⥒] LessFullEqual;[≦] LeftArrowBar;[⇤] LeftTeeArrow;[↤] LeftTriangle;[⊲] LeftUpVector;[↿] LeftCeiling;[⌈] LessGreater;[≶] LeftVector;[↼] LeftArrow;[←] LeftFloor;[⌊] Leftarrow;[⇐] LessTilde;[≲] LessLess;[⪡] LeftTee;[⊣]. - "\x12ftArrowRightArrow;\x03⇆\x10ftDoubleBracket;\x03⟦\x10ftDownTeeVector;\x03⥡\x10ftDownVectorBar;\x03⥙\x10ftTriangleEqual;\x03⊴\x0fftAngleBracket;\x03⟨\x0fftUpDownVector;\x03⥑\x0fssEqualGreater;\x03⋚\x0eftRightVector;\x03⥎\x0eftTriangleBar;\x03⧏\x0eftUpTeeVector;\x03⥠\x0eftUpVectorBar;\x03⥘\x0dftDownVector;\x03⇃\x0dftRightArrow;\x03↔\x0dftrightarrow;\x03⇔\x0dssSlantEqual;\x03⩽\x0cftTeeVector;\x03⥚\x0cftVectorBar;\x03⥒\x0cssFullEqual;\x03≦\x0bftArrowBar;\x03⇤\x0bftTeeArrow;\x03↤\x0bftTriangle;\x03⊲\x0bftUpVector;\x03↿\x0aftCeiling;\x03⌈\x0assGreater;\x03≶\x09ftVector;\x03↼\x08ftArrow;\x03←\x08ftFloor;\x03⌊\x08ftarrow;\x03⇐\x08ssTilde;\x03≲\x07ssLess;\x03⪡\x06ftTee;\x03⊣", - // Lfr;[𝔏]. - "\x02r;\x04𝔏", - // Lleftarrow;[⇚] Ll;[⋘]. - "\x09eftarrow;\x03⇚\x01;\x03⋘", - // Lmidot;[Ŀ]. - "\x05idot;\x02Ŀ", - // LongLeftRightArrow;[⟷] Longleftrightarrow;[⟺] LowerRightArrow;[↘] LongRightArrow;[⟶] Longrightarrow;[⟹] LowerLeftArrow;[↙] LongLeftArrow;[⟵] Longleftarrow;[⟸] Lopf;[𝕃]. - "\x11ngLeftRightArrow;\x03⟷\x11ngleftrightarrow;\x03⟺\x0ewerRightArrow;\x03↘\x0dngRightArrow;\x03⟶\x0dngrightarrow;\x03⟹\x0dwerLeftArrow;\x03↙\x0cngLeftArrow;\x03⟵\x0cngleftarrow;\x03⟸\x03pf;\x04𝕃", - // Lstrok;[Ł] Lscr;[ℒ] Lsh;[↰]. - "\x05trok;\x02Ł\x03cr;\x03ℒ\x02h;\x03↰", - // Lt;[≪]. - "\x01;\x03≪", - // Map;[⤅]. - "\x02p;\x03⤅", - // Mcy;[М]. - "\x02y;\x02М", - // MediumSpace;[ ] Mellintrf;[ℳ]. - "\x0adiumSpace;\x03 \x08llintrf;\x03ℳ", - // Mfr;[𝔐]. - "\x02r;\x04𝔐", - // MinusPlus;[∓]. - "\x08nusPlus;\x03∓", - // Mopf;[𝕄]. - "\x03pf;\x04𝕄", - // Mscr;[ℳ]. - "\x03cr;\x03ℳ", - // Mu;[Μ]. - "\x01;\x02Μ", - // NJcy;[Њ]. - "\x03cy;\x02Њ", - // Nacute;[Ń]. - "\x05cute;\x02Ń", - // Ncaron;[Ň] Ncedil;[Ņ] Ncy;[Н]. - "\x05aron;\x02Ň\x05edil;\x02Ņ\x02y;\x02Н", - // NegativeVeryThinSpace;[​] NestedGreaterGreater;[≫] NegativeMediumSpace;[​] NegativeThickSpace;[​] NegativeThinSpace;[​] NestedLessLess;[≪] NewLine;[\xa]. - "\x14gativeVeryThinSpace;\x03​\x13stedGreaterGreater;\x03≫\x12gativeMediumSpace;\x03​\x11gativeThickSpace;\x03​\x10gativeThinSpace;\x03​\x0dstedLessLess;\x03≪\x06wLine;\x01\xa", - // Nfr;[𝔑]. - "\x02r;\x04𝔑", - // NotNestedGreaterGreater;[⪢̸] NotSquareSupersetEqual;[⋣] NotPrecedesSlantEqual;[⋠] NotRightTriangleEqual;[⋭] NotSucceedsSlantEqual;[⋡] NotDoubleVerticalBar;[∦] NotGreaterSlantEqual;[⩾̸] NotLeftTriangleEqual;[⋬] NotSquareSubsetEqual;[⋢] NotGreaterFullEqual;[≧̸] NotRightTriangleBar;[⧐̸] NotLeftTriangleBar;[⧏̸] NotGreaterGreater;[≫̸] NotLessSlantEqual;[⩽̸] NotNestedLessLess;[⪡̸] NotReverseElement;[∌] NotSquareSuperset;[⊐̸] NotTildeFullEqual;[≇] NonBreakingSpace;[ ] NotPrecedesEqual;[⪯̸] NotRightTriangle;[⋫] NotSucceedsEqual;[⪰̸] NotSucceedsTilde;[≿̸] NotSupersetEqual;[⊉] NotGreaterEqual;[≱] NotGreaterTilde;[≵] NotHumpDownHump;[≎̸] NotLeftTriangle;[⋪] NotSquareSubset;[⊏̸] NotGreaterLess;[≹] NotLessGreater;[≸] NotSubsetEqual;[⊈] NotVerticalBar;[∤] NotEqualTilde;[≂̸] NotTildeEqual;[≄] NotTildeTilde;[≉] NotCongruent;[≢] NotHumpEqual;[≏̸] NotLessEqual;[≰] NotLessTilde;[≴] NotLessLess;[≪̸] NotPrecedes;[⊀] NotSucceeds;[⊁] NotSuperset;[⊃⃒] NotElement;[∉] NotGreater;[≯] NotCupCap;[≭] NotExists;[∄] NotSubset;[⊂⃒] NotEqual;[≠] NotTilde;[≁] NoBreak;[⁠] NotLess;[≮] Nopf;[ℕ] Not;[⫬]. - "\x16tNestedGreaterGreater;\x05⪢̸\x15tSquareSupersetEqual;\x03⋣\x14tPrecedesSlantEqual;\x03⋠\x14tRightTriangleEqual;\x03⋭\x14tSucceedsSlantEqual;\x03⋡\x13tDoubleVerticalBar;\x03∦\x13tGreaterSlantEqual;\x05⩾̸\x13tLeftTriangleEqual;\x03⋬\x13tSquareSubsetEqual;\x03⋢\x12tGreaterFullEqual;\x05≧̸\x12tRightTriangleBar;\x05⧐̸\x11tLeftTriangleBar;\x05⧏̸\x10tGreaterGreater;\x05≫̸\x10tLessSlantEqual;\x05⩽̸\x10tNestedLessLess;\x05⪡̸\x10tReverseElement;\x03∌\x10tSquareSuperset;\x05⊐̸\x10tTildeFullEqual;\x03≇\x0fnBreakingSpace;\x02 \x0ftPrecedesEqual;\x05⪯̸\x0ftRightTriangle;\x03⋫\x0ftSucceedsEqual;\x05⪰̸\x0ftSucceedsTilde;\x05≿̸\x0ftSupersetEqual;\x03⊉\x0etGreaterEqual;\x03≱\x0etGreaterTilde;\x03≵\x0etHumpDownHump;\x05≎̸\x0etLeftTriangle;\x03⋪\x0etSquareSubset;\x05⊏̸\x0dtGreaterLess;\x03≹\x0dtLessGreater;\x03≸\x0dtSubsetEqual;\x03⊈\x0dtVerticalBar;\x03∤\x0ctEqualTilde;\x05≂̸\x0ctTildeEqual;\x03≄\x0ctTildeTilde;\x03≉\x0btCongruent;\x03≢\x0btHumpEqual;\x05≏̸\x0btLessEqual;\x03≰\x0btLessTilde;\x03≴\x0atLessLess;\x05≪̸\x0atPrecedes;\x03⊀\x0atSucceeds;\x03⊁\x0atSuperset;\x06⊃⃒\x09tElement;\x03∉\x09tGreater;\x03≯\x08tCupCap;\x03≭\x08tExists;\x03∄\x08tSubset;\x06⊂⃒\x07tEqual;\x03≠\x07tTilde;\x03≁\x06Break;\x03⁠\x06tLess;\x03≮\x03pf;\x03ℕ\x02t;\x03⫬", - // Nscr;[𝒩]. - "\x03cr;\x04𝒩", - // Ntilde;[Ñ] Ntilde[Ñ]. - "\x05ilde;\x02Ñ\x04ilde\x02Ñ", - // Nu;[Ν]. - "\x01;\x02Ν", - // OElig;[Œ]. - "\x04lig;\x02Œ", - // Oacute;[Ó] Oacute[Ó]. - "\x05cute;\x02Ó\x04cute\x02Ó", - // Ocirc;[Ô] Ocirc[Ô] Ocy;[О]. - "\x04irc;\x02Ô\x03irc\x02Ô\x02y;\x02О", - // Odblac;[Ő]. - "\x05blac;\x02Ő", - // Ofr;[𝔒]. - "\x02r;\x04𝔒", - // Ograve;[Ò] Ograve[Ò]. - "\x05rave;\x02Ò\x04rave\x02Ò", - // Omicron;[Ο] Omacr;[Ō] Omega;[Ω]. - "\x06icron;\x02Ο\x04acr;\x02Ō\x04ega;\x02Ω", - // Oopf;[𝕆]. - "\x03pf;\x04𝕆", - // OpenCurlyDoubleQuote;[“] OpenCurlyQuote;[‘]. - "\x13enCurlyDoubleQuote;\x03“\x0denCurlyQuote;\x03‘", - // Or;[⩔]. - "\x01;\x03⩔", - // Oslash;[Ø] Oslash[Ø] Oscr;[𝒪]. - "\x05lash;\x02Ø\x04lash\x02Ø\x03cr;\x04𝒪", - // Otilde;[Õ] Otimes;[⨷] Otilde[Õ]. - "\x05ilde;\x02Õ\x05imes;\x03⨷\x04ilde\x02Õ", - // Ouml;[Ö] Ouml[Ö]. - "\x03ml;\x02Ö\x02ml\x02Ö", - // OverParenthesis;[⏜] OverBracket;[⎴] OverBrace;[⏞] OverBar;[‾]. - "\x0eerParenthesis;\x03⏜\x0aerBracket;\x03⎴\x08erBrace;\x03⏞\x06erBar;\x03‾", - // PartialD;[∂]. - "\x07rtialD;\x03∂", - // Pcy;[П]. - "\x02y;\x02П", - // Pfr;[𝔓]. - "\x02r;\x04𝔓", - // Phi;[Φ]. - "\x02i;\x02Φ", - // Pi;[Π]. - "\x01;\x02Π", - // PlusMinus;[±]. - "\x08usMinus;\x02±", - // Poincareplane;[ℌ] Popf;[ℙ]. - "\x0cincareplane;\x03ℌ\x03pf;\x03ℙ", - // PrecedesSlantEqual;[≼] PrecedesEqual;[⪯] PrecedesTilde;[≾] Proportional;[∝] Proportion;[∷] Precedes;[≺] Product;[∏] Prime;[″] Pr;[⪻]. - "\x11ecedesSlantEqual;\x03≼\x0cecedesEqual;\x03⪯\x0cecedesTilde;\x03≾\x0boportional;\x03∝\x09oportion;\x03∷\x07ecedes;\x03≺\x06oduct;\x03∏\x04ime;\x03″\x01;\x03⪻", - // Pscr;[𝒫] Psi;[Ψ]. - "\x03cr;\x04𝒫\x02i;\x02Ψ", - // QUOT;[\"] QUOT[\"]. - "\x03OT;\x01\"\x02OT\x01\"", - // Qfr;[𝔔]. - "\x02r;\x04𝔔", - // Qopf;[ℚ]. - "\x03pf;\x03ℚ", - // Qscr;[𝒬]. - "\x03cr;\x04𝒬", - // RBarr;[⤐]. - "\x04arr;\x03⤐", - // REG;[®] REG[®]. - "\x02G;\x02®\x01G\x02®", - // Racute;[Ŕ] Rarrtl;[⤖] Rang;[⟫] Rarr;[↠]. - "\x05cute;\x02Ŕ\x05rrtl;\x03⤖\x03ng;\x03⟫\x03rr;\x03↠", - // Rcaron;[Ř] Rcedil;[Ŗ] Rcy;[Р]. - "\x05aron;\x02Ř\x05edil;\x02Ŗ\x02y;\x02Р", - // ReverseUpEquilibrium;[⥯] ReverseEquilibrium;[⇋] ReverseElement;[∋] Re;[ℜ]. - "\x13verseUpEquilibrium;\x03⥯\x11verseEquilibrium;\x03⇋\x0dverseElement;\x03∋\x01;\x03ℜ", - // Rfr;[ℜ]. - "\x02r;\x03ℜ", - // Rho;[Ρ]. - "\x02o;\x02Ρ", - // RightArrowLeftArrow;[⇄] RightDoubleBracket;[⟧] RightDownTeeVector;[⥝] RightDownVectorBar;[⥕] RightTriangleEqual;[⊵] RightAngleBracket;[⟩] RightUpDownVector;[⥏] RightTriangleBar;[⧐] RightUpTeeVector;[⥜] RightUpVectorBar;[⥔] RightDownVector;[⇂] RightTeeVector;[⥛] RightVectorBar;[⥓] RightArrowBar;[⇥] RightTeeArrow;[↦] RightTriangle;[⊳] RightUpVector;[↾] RightCeiling;[⌉] RightVector;[⇀] RightArrow;[→] RightFloor;[⌋] Rightarrow;[⇒] RightTee;[⊢]. - "\x12ghtArrowLeftArrow;\x03⇄\x11ghtDoubleBracket;\x03⟧\x11ghtDownTeeVector;\x03⥝\x11ghtDownVectorBar;\x03⥕\x11ghtTriangleEqual;\x03⊵\x10ghtAngleBracket;\x03⟩\x10ghtUpDownVector;\x03⥏\x0fghtTriangleBar;\x03⧐\x0fghtUpTeeVector;\x03⥜\x0fghtUpVectorBar;\x03⥔\x0eghtDownVector;\x03⇂\x0dghtTeeVector;\x03⥛\x0dghtVectorBar;\x03⥓\x0cghtArrowBar;\x03⇥\x0cghtTeeArrow;\x03↦\x0cghtTriangle;\x03⊳\x0cghtUpVector;\x03↾\x0bghtCeiling;\x03⌉\x0aghtVector;\x03⇀\x09ghtArrow;\x03→\x09ghtFloor;\x03⌋\x09ghtarrow;\x03⇒\x07ghtTee;\x03⊢", - // RoundImplies;[⥰] Ropf;[ℝ]. - "\x0bundImplies;\x03⥰\x03pf;\x03ℝ", - // Rrightarrow;[⇛]. - "\x0aightarrow;\x03⇛", - // Rscr;[ℛ] Rsh;[↱]. - "\x03cr;\x03ℛ\x02h;\x03↱", - // RuleDelayed;[⧴]. - "\x0aleDelayed;\x03⧴", - // SHCHcy;[Щ] SHcy;[Ш]. - "\x05CHcy;\x02Щ\x03cy;\x02Ш", - // SOFTcy;[Ь]. - "\x05FTcy;\x02Ь", - // Sacute;[Ś]. - "\x05cute;\x02Ś", - // Scaron;[Š] Scedil;[Ş] Scirc;[Ŝ] Scy;[С] Sc;[⪼]. - "\x05aron;\x02Š\x05edil;\x02Ş\x04irc;\x02Ŝ\x02y;\x02С\x01;\x03⪼", - // Sfr;[𝔖]. - "\x02r;\x04𝔖", - // ShortRightArrow;[→] ShortDownArrow;[↓] ShortLeftArrow;[←] ShortUpArrow;[↑]. - "\x0eortRightArrow;\x03→\x0dortDownArrow;\x03↓\x0dortLeftArrow;\x03←\x0bortUpArrow;\x03↑", - // Sigma;[Σ]. - "\x04gma;\x02Σ", - // SmallCircle;[∘]. - "\x0aallCircle;\x03∘", - // Sopf;[𝕊]. - "\x03pf;\x04𝕊", - // SquareSupersetEqual;[⊒] SquareIntersection;[⊓] SquareSubsetEqual;[⊑] SquareSuperset;[⊐] SquareSubset;[⊏] SquareUnion;[⊔] Square;[□] Sqrt;[√]. - "\x12uareSupersetEqual;\x03⊒\x11uareIntersection;\x03⊓\x10uareSubsetEqual;\x03⊑\x0duareSuperset;\x03⊐\x0buareSubset;\x03⊏\x0auareUnion;\x03⊔\x05uare;\x03□\x03rt;\x03√", - // Sscr;[𝒮]. - "\x03cr;\x04𝒮", - // Star;[⋆]. - "\x03ar;\x03⋆", - // SucceedsSlantEqual;[≽] SucceedsEqual;[⪰] SucceedsTilde;[≿] SupersetEqual;[⊇] SubsetEqual;[⊆] Succeeds;[≻] SuchThat;[∋] Superset;[⊃] Subset;[⋐] Supset;[⋑] Sub;[⋐] Sum;[∑] Sup;[⋑]. - "\x11cceedsSlantEqual;\x03≽\x0ccceedsEqual;\x03⪰\x0ccceedsTilde;\x03≿\x0cpersetEqual;\x03⊇\x0absetEqual;\x03⊆\x07cceeds;\x03≻\x07chThat;\x03∋\x07perset;\x03⊃\x05bset;\x03⋐\x05pset;\x03⋑\x02b;\x03⋐\x02m;\x03∑\x02p;\x03⋑", - // THORN;[Þ] THORN[Þ]. - "\x04ORN;\x02Þ\x03ORN\x02Þ", - // TRADE;[™]. - "\x04ADE;\x03™", - // TSHcy;[Ћ] TScy;[Ц]. - "\x04Hcy;\x02Ћ\x03cy;\x02Ц", - // Tab;[\x9] Tau;[Τ]. - "\x02b;\x01\x9\x02u;\x02Τ", - // Tcaron;[Ť] Tcedil;[Ţ] Tcy;[Т]. - "\x05aron;\x02Ť\x05edil;\x02Ţ\x02y;\x02Т", - // Tfr;[𝔗]. - "\x02r;\x04𝔗", - // ThickSpace;[  ] Therefore;[∴] ThinSpace;[ ] Theta;[Θ]. - "\x09ickSpace;\x06  \x08erefore;\x03∴\x08inSpace;\x03 \x04eta;\x02Θ", - // TildeFullEqual;[≅] TildeEqual;[≃] TildeTilde;[≈] Tilde;[∼]. - "\x0dldeFullEqual;\x03≅\x09ldeEqual;\x03≃\x09ldeTilde;\x03≈\x04lde;\x03∼", - // Topf;[𝕋]. - "\x03pf;\x04𝕋", - // TripleDot;[⃛]. - "\x08ipleDot;\x03⃛", - // Tstrok;[Ŧ] Tscr;[𝒯]. - "\x05trok;\x02Ŧ\x03cr;\x04𝒯", - // Uarrocir;[⥉] Uacute;[Ú] Uacute[Ú] Uarr;[↟]. - "\x07rrocir;\x03⥉\x05cute;\x02Ú\x04cute\x02Ú\x03rr;\x03↟", - // Ubreve;[Ŭ] Ubrcy;[Ў]. - "\x05reve;\x02Ŭ\x04rcy;\x02Ў", - // Ucirc;[Û] Ucirc[Û] Ucy;[У]. - "\x04irc;\x02Û\x03irc\x02Û\x02y;\x02У", - // Udblac;[Ű]. - "\x05blac;\x02Ű", - // Ufr;[𝔘]. - "\x02r;\x04𝔘", - // Ugrave;[Ù] Ugrave[Ù]. - "\x05rave;\x02Ù\x04rave\x02Ù", - // Umacr;[Ū]. - "\x04acr;\x02Ū", - // UnderParenthesis;[⏝] UnderBracket;[⎵] UnderBrace;[⏟] UnionPlus;[⊎] UnderBar;[_] Union;[⋃]. - "\x0fderParenthesis;\x03⏝\x0bderBracket;\x03⎵\x09derBrace;\x03⏟\x08ionPlus;\x03⊎\x07derBar;\x01_\x04ion;\x03⋃", - // Uogon;[Ų] Uopf;[𝕌]. - "\x04gon;\x02Ų\x03pf;\x04𝕌", - // UpArrowDownArrow;[⇅] UpperRightArrow;[↗] UpperLeftArrow;[↖] UpEquilibrium;[⥮] UpDownArrow;[↕] Updownarrow;[⇕] UpArrowBar;[⤒] UpTeeArrow;[↥] UpArrow;[↑] Uparrow;[⇑] Upsilon;[Υ] UpTee;[⊥] Upsi;[ϒ]. - "\x0fArrowDownArrow;\x03⇅\x0eperRightArrow;\x03↗\x0dperLeftArrow;\x03↖\x0cEquilibrium;\x03⥮\x0aDownArrow;\x03↕\x0adownarrow;\x03⇕\x09ArrowBar;\x03⤒\x09TeeArrow;\x03↥\x06Arrow;\x03↑\x06arrow;\x03⇑\x06silon;\x02Υ\x04Tee;\x03⊥\x03si;\x02ϒ", - // Uring;[Ů]. - "\x04ing;\x02Ů", - // Uscr;[𝒰]. - "\x03cr;\x04𝒰", - // Utilde;[Ũ]. - "\x05ilde;\x02Ũ", - // Uuml;[Ü] Uuml[Ü]. - "\x03ml;\x02Ü\x02ml\x02Ü", - // VDash;[⊫]. - "\x04ash;\x03⊫", - // Vbar;[⫫]. - "\x03ar;\x03⫫", - // Vcy;[В]. - "\x02y;\x02В", - // Vdashl;[⫦] Vdash;[⊩]. - "\x05ashl;\x03⫦\x04ash;\x03⊩", - // VerticalSeparator;[❘] VerticalTilde;[≀] VeryThinSpace;[ ] VerticalLine;[|] VerticalBar;[∣] Verbar;[‖] Vert;[‖] Vee;[⋁]. - "\x10rticalSeparator;\x03❘\x0crticalTilde;\x03≀\x0cryThinSpace;\x03 \x0brticalLine;\x01|\x0articalBar;\x03∣\x05rbar;\x03‖\x03rt;\x03‖\x02e;\x03⋁", - // Vfr;[𝔙]. - "\x02r;\x04𝔙", - // Vopf;[𝕍]. - "\x03pf;\x04𝕍", - // Vscr;[𝒱]. - "\x03cr;\x04𝒱", - // Vvdash;[⊪]. - "\x05dash;\x03⊪", - // Wcirc;[Ŵ]. - "\x04irc;\x02Ŵ", - // Wedge;[⋀]. - "\x04dge;\x03⋀", - // Wfr;[𝔚]. - "\x02r;\x04𝔚", - // Wopf;[𝕎]. - "\x03pf;\x04𝕎", - // Wscr;[𝒲]. - "\x03cr;\x04𝒲", - // Xfr;[𝔛]. - "\x02r;\x04𝔛", - // Xi;[Ξ]. - "\x01;\x02Ξ", - // Xopf;[𝕏]. - "\x03pf;\x04𝕏", - // Xscr;[𝒳]. - "\x03cr;\x04𝒳", - // YAcy;[Я]. - "\x03cy;\x02Я", - // YIcy;[Ї]. - "\x03cy;\x02Ї", - // YUcy;[Ю]. - "\x03cy;\x02Ю", - // Yacute;[Ý] Yacute[Ý]. - "\x05cute;\x02Ý\x04cute\x02Ý", - // Ycirc;[Ŷ] Ycy;[Ы]. - "\x04irc;\x02Ŷ\x02y;\x02Ы", - // Yfr;[𝔜]. - "\x02r;\x04𝔜", - // Yopf;[𝕐]. - "\x03pf;\x04𝕐", - // Yscr;[𝒴]. - "\x03cr;\x04𝒴", - // Yuml;[Ÿ]. - "\x03ml;\x02Ÿ", - // ZHcy;[Ж]. - "\x03cy;\x02Ж", - // Zacute;[Ź]. - "\x05cute;\x02Ź", - // Zcaron;[Ž] Zcy;[З]. - "\x05aron;\x02Ž\x02y;\x02З", - // Zdot;[Ż]. - "\x03ot;\x02Ż", - // ZeroWidthSpace;[​] Zeta;[Ζ]. - "\x0droWidthSpace;\x03​\x03ta;\x02Ζ", - // Zfr;[ℨ]. - "\x02r;\x03ℨ", - // Zopf;[ℤ]. - "\x03pf;\x03ℤ", - // Zscr;[𝒵]. - "\x03cr;\x04𝒵", - // aacute;[á] aacute[á]. - "\x05cute;\x02á\x04cute\x02á", - // abreve;[ă]. - "\x05reve;\x02ă", - // acirc;[â] acute;[´] acirc[â] acute[´] acE;[∾̳] acd;[∿] acy;[а] ac;[∾]. - "\x04irc;\x02â\x04ute;\x02´\x03irc\x02â\x03ute\x02´\x02E;\x05∾̳\x02d;\x03∿\x02y;\x02а\x01;\x03∾", - // aelig;[æ] aelig[æ]. - "\x04lig;\x02æ\x03lig\x02æ", - // afr;[𝔞] af;[⁡]. - "\x02r;\x04𝔞\x01;\x03⁡", - // agrave;[à] agrave[à]. - "\x05rave;\x02à\x04rave\x02à", - // alefsym;[ℵ] aleph;[ℵ] alpha;[α]. - "\x06efsym;\x03ℵ\x04eph;\x03ℵ\x04pha;\x02α", - // amacr;[ā] amalg;[⨿] amp;[&] amp[&]. - "\x04acr;\x02ā\x04alg;\x03⨿\x02p;\x01&\x01p\x01&", - // andslope;[⩘] angmsdaa;[⦨] angmsdab;[⦩] angmsdac;[⦪] angmsdad;[⦫] angmsdae;[⦬] angmsdaf;[⦭] angmsdag;[⦮] angmsdah;[⦯] angrtvbd;[⦝] angrtvb;[⊾] angzarr;[⍼] andand;[⩕] angmsd;[∡] angsph;[∢] angle;[∠] angrt;[∟] angst;[Å] andd;[⩜] andv;[⩚] ange;[⦤] and;[∧] ang;[∠]. - "\x07dslope;\x03⩘\x07gmsdaa;\x03⦨\x07gmsdab;\x03⦩\x07gmsdac;\x03⦪\x07gmsdad;\x03⦫\x07gmsdae;\x03⦬\x07gmsdaf;\x03⦭\x07gmsdag;\x03⦮\x07gmsdah;\x03⦯\x07grtvbd;\x03⦝\x06grtvb;\x03⊾\x06gzarr;\x03⍼\x05dand;\x03⩕\x05gmsd;\x03∡\x05gsph;\x03∢\x04gle;\x03∠\x04grt;\x03∟\x04gst;\x02Å\x03dd;\x03⩜\x03dv;\x03⩚\x03ge;\x03⦤\x02d;\x03∧\x02g;\x03∠", - // aogon;[ą] aopf;[𝕒]. - "\x04gon;\x02ą\x03pf;\x04𝕒", - // approxeq;[≊] apacir;[⩯] approx;[≈] apid;[≋] apos;['] apE;[⩰] ape;[≊] ap;[≈]. - "\x07proxeq;\x03≊\x05acir;\x03⩯\x05prox;\x03≈\x03id;\x03≋\x03os;\x01'\x02E;\x03⩰\x02e;\x03≊\x01;\x03≈", - // aring;[å] aring[å]. - "\x04ing;\x02å\x03ing\x02å", - // asympeq;[≍] asymp;[≈] ascr;[𝒶] ast;[*]. - "\x06ympeq;\x03≍\x04ymp;\x03≈\x03cr;\x04𝒶\x02t;\x01*", - // atilde;[ã] atilde[ã]. - "\x05ilde;\x02ã\x04ilde\x02ã", - // auml;[ä] auml[ä]. - "\x03ml;\x02ä\x02ml\x02ä", - // awconint;[∳] awint;[⨑]. - "\x07conint;\x03∳\x04int;\x03⨑", - // bNot;[⫭]. - "\x03ot;\x03⫭", - // backepsilon;[϶] backprime;[‵] backsimeq;[⋍] backcong;[≌] barwedge;[⌅] backsim;[∽] barvee;[⊽] barwed;[⌅]. - "\x0ackepsilon;\x02϶\x08ckprime;\x03‵\x08cksimeq;\x03⋍\x07ckcong;\x03≌\x07rwedge;\x03⌅\x06cksim;\x03∽\x05rvee;\x03⊽\x05rwed;\x03⌅", - // bbrktbrk;[⎶] bbrk;[⎵]. - "\x07rktbrk;\x03⎶\x03rk;\x03⎵", - // bcong;[≌] bcy;[б]. - "\x04ong;\x03≌\x02y;\x02б", - // bdquo;[„]. - "\x04quo;\x03„", - // because;[∵] bemptyv;[⦰] between;[≬] becaus;[∵] bernou;[ℬ] bepsi;[϶] beta;[β] beth;[ℶ]. - "\x06cause;\x03∵\x06mptyv;\x03⦰\x06tween;\x03≬\x05caus;\x03∵\x05rnou;\x03ℬ\x04psi;\x02϶\x03ta;\x02β\x03th;\x03ℶ", - // bfr;[𝔟]. - "\x02r;\x04𝔟", - // bigtriangledown;[▽] bigtriangleup;[△] bigotimes;[⨂] bigoplus;[⨁] bigsqcup;[⨆] biguplus;[⨄] bigwedge;[⋀] bigcirc;[◯] bigodot;[⨀] bigstar;[★] bigcap;[⋂] bigcup;[⋃] bigvee;[⋁]. - "\x0egtriangledown;\x03▽\x0cgtriangleup;\x03△\x08gotimes;\x03⨂\x07goplus;\x03⨁\x07gsqcup;\x03⨆\x07guplus;\x03⨄\x07gwedge;\x03⋀\x06gcirc;\x03◯\x06godot;\x03⨀\x06gstar;\x03★\x05gcap;\x03⋂\x05gcup;\x03⋃\x05gvee;\x03⋁", - // bkarow;[⤍]. - "\x05arow;\x03⤍", - // blacktriangleright;[▸] blacktriangledown;[▾] blacktriangleleft;[◂] blacktriangle;[▴] blacklozenge;[⧫] blacksquare;[▪] blank;[␣] blk12;[▒] blk14;[░] blk34;[▓] block;[█]. - "\x11acktriangleright;\x03▸\x10acktriangledown;\x03▾\x10acktriangleleft;\x03◂\x0cacktriangle;\x03▴\x0backlozenge;\x03⧫\x0aacksquare;\x03▪\x04ank;\x03␣\x04k12;\x03▒\x04k14;\x03░\x04k34;\x03▓\x04ock;\x03█", - // bnequiv;[≡⃥] bnot;[⌐] bne;[=⃥]. - "\x06equiv;\x06≡⃥\x03ot;\x03⌐\x02e;\x04=⃥", - // boxminus;[⊟] boxtimes;[⊠] boxplus;[⊞] bottom;[⊥] bowtie;[⋈] boxbox;[⧉] boxDL;[╗] boxDR;[╔] boxDl;[╖] boxDr;[╓] boxHD;[╦] boxHU;[╩] boxHd;[╤] boxHu;[╧] boxUL;[╝] boxUR;[╚] boxUl;[╜] boxUr;[╙] boxVH;[╬] boxVL;[╣] boxVR;[╠] boxVh;[╫] boxVl;[╢] boxVr;[╟] boxdL;[╕] boxdR;[╒] boxdl;[┐] boxdr;[┌] boxhD;[╥] boxhU;[╨] boxhd;[┬] boxhu;[┴] boxuL;[╛] boxuR;[╘] boxul;[┘] boxur;[└] boxvH;[╪] boxvL;[╡] boxvR;[╞] boxvh;[┼] boxvl;[┤] boxvr;[├] bopf;[𝕓] boxH;[═] boxV;[║] boxh;[─] boxv;[│] bot;[⊥]. - "\x07xminus;\x03⊟\x07xtimes;\x03⊠\x06xplus;\x03⊞\x05ttom;\x03⊥\x05wtie;\x03⋈\x05xbox;\x03⧉\x04xDL;\x03╗\x04xDR;\x03╔\x04xDl;\x03╖\x04xDr;\x03╓\x04xHD;\x03╦\x04xHU;\x03╩\x04xHd;\x03╤\x04xHu;\x03╧\x04xUL;\x03╝\x04xUR;\x03╚\x04xUl;\x03╜\x04xUr;\x03╙\x04xVH;\x03╬\x04xVL;\x03╣\x04xVR;\x03╠\x04xVh;\x03╫\x04xVl;\x03╢\x04xVr;\x03╟\x04xdL;\x03╕\x04xdR;\x03╒\x04xdl;\x03┐\x04xdr;\x03┌\x04xhD;\x03╥\x04xhU;\x03╨\x04xhd;\x03┬\x04xhu;\x03┴\x04xuL;\x03╛\x04xuR;\x03╘\x04xul;\x03┘\x04xur;\x03└\x04xvH;\x03╪\x04xvL;\x03╡\x04xvR;\x03╞\x04xvh;\x03┼\x04xvl;\x03┤\x04xvr;\x03├\x03pf;\x04𝕓\x03xH;\x03═\x03xV;\x03║\x03xh;\x03─\x03xv;\x03│\x02t;\x03⊥", - // bprime;[‵]. - "\x05rime;\x03‵", - // brvbar;[¦] breve;[˘] brvbar[¦]. - "\x05vbar;\x02¦\x04eve;\x02˘\x04vbar\x02¦", - // bsolhsub;[⟈] bsemi;[⁏] bsime;[⋍] bsolb;[⧅] bscr;[𝒷] bsim;[∽] bsol;[\\]. - "\x07olhsub;\x03⟈\x04emi;\x03⁏\x04ime;\x03⋍\x04olb;\x03⧅\x03cr;\x04𝒷\x03im;\x03∽\x03ol;\x01\\", - // bullet;[•] bumpeq;[≏] bumpE;[⪮] bumpe;[≏] bull;[•] bump;[≎]. - "\x05llet;\x03•\x05mpeq;\x03≏\x04mpE;\x03⪮\x04mpe;\x03≏\x03ll;\x03•\x03mp;\x03≎", - // capbrcup;[⩉] cacute;[ć] capand;[⩄] capcap;[⩋] capcup;[⩇] capdot;[⩀] caret;[⁁] caron;[ˇ] caps;[∩︀] cap;[∩]. - "\x07pbrcup;\x03⩉\x05cute;\x02ć\x05pand;\x03⩄\x05pcap;\x03⩋\x05pcup;\x03⩇\x05pdot;\x03⩀\x04ret;\x03⁁\x04ron;\x02ˇ\x03ps;\x06∩︀\x02p;\x03∩", - // ccupssm;[⩐] ccaron;[č] ccedil;[ç] ccaps;[⩍] ccedil[ç] ccirc;[ĉ] ccups;[⩌]. - "\x06upssm;\x03⩐\x05aron;\x02č\x05edil;\x02ç\x04aps;\x03⩍\x04edil\x02ç\x04irc;\x02ĉ\x04ups;\x03⩌", - // cdot;[ċ]. - "\x03ot;\x02ċ", - // centerdot;[·] cemptyv;[⦲] cedil;[¸] cedil[¸] cent;[¢] cent[¢]. - "\x08nterdot;\x02·\x06mptyv;\x03⦲\x04dil;\x02¸\x03dil\x02¸\x03nt;\x02¢\x02nt\x02¢", - // cfr;[𝔠]. - "\x02r;\x04𝔠", - // checkmark;[✓] check;[✓] chcy;[ч] chi;[χ]. - "\x08eckmark;\x03✓\x04eck;\x03✓\x03cy;\x02ч\x02i;\x02χ", - // circlearrowright;[↻] circlearrowleft;[↺] circledcirc;[⊚] circleddash;[⊝] circledast;[⊛] circledR;[®] circledS;[Ⓢ] cirfnint;[⨐] cirscir;[⧂] circeq;[≗] cirmid;[⫯] cirE;[⧃] circ;[ˆ] cire;[≗] cir;[○]. - "\x0frclearrowright;\x03↻\x0erclearrowleft;\x03↺\x0arcledcirc;\x03⊚\x0arcleddash;\x03⊝\x09rcledast;\x03⊛\x07rcledR;\x02®\x07rcledS;\x03Ⓢ\x07rfnint;\x03⨐\x06rscir;\x03⧂\x05rceq;\x03≗\x05rmid;\x03⫯\x03rE;\x03⧃\x03rc;\x02ˆ\x03re;\x03≗\x02r;\x03○", - // clubsuit;[♣] clubs;[♣]. - "\x07ubsuit;\x03♣\x04ubs;\x03♣", - // complement;[∁] complexes;[ℂ] coloneq;[≔] congdot;[⩭] colone;[≔] commat;[@] compfn;[∘] conint;[∮] coprod;[∐] copysr;[℗] colon;[:] comma;[,] comp;[∁] cong;[≅] copf;[𝕔] copy;[©] copy[©]. - "\x09mplement;\x03∁\x08mplexes;\x03ℂ\x06loneq;\x03≔\x06ngdot;\x03⩭\x05lone;\x03≔\x05mmat;\x01@\x05mpfn;\x03∘\x05nint;\x03∮\x05prod;\x03∐\x05pysr;\x03℗\x04lon;\x01:\x04mma;\x01,\x03mp;\x03∁\x03ng;\x03≅\x03pf;\x04𝕔\x03py;\x02©\x02py\x02©", - // crarr;[↵] cross;[✗]. - "\x04arr;\x03↵\x04oss;\x03✗", - // csube;[⫑] csupe;[⫒] cscr;[𝒸] csub;[⫏] csup;[⫐]. - "\x04ube;\x03⫑\x04upe;\x03⫒\x03cr;\x04𝒸\x03ub;\x03⫏\x03up;\x03⫐", - // ctdot;[⋯]. - "\x04dot;\x03⋯", - // curvearrowright;[↷] curvearrowleft;[↶] curlyeqprec;[⋞] curlyeqsucc;[⋟] curlywedge;[⋏] cupbrcap;[⩈] curlyvee;[⋎] cudarrl;[⤸] cudarrr;[⤵] cularrp;[⤽] curarrm;[⤼] cularr;[↶] cupcap;[⩆] cupcup;[⩊] cupdot;[⊍] curarr;[↷] curren;[¤] cuepr;[⋞] cuesc;[⋟] cupor;[⩅] curren[¤] cuvee;[⋎] cuwed;[⋏] cups;[∪︀] cup;[∪]. - "\x0ervearrowright;\x03↷\x0drvearrowleft;\x03↶\x0arlyeqprec;\x03⋞\x0arlyeqsucc;\x03⋟\x09rlywedge;\x03⋏\x07pbrcap;\x03⩈\x07rlyvee;\x03⋎\x06darrl;\x03⤸\x06darrr;\x03⤵\x06larrp;\x03⤽\x06rarrm;\x03⤼\x05larr;\x03↶\x05pcap;\x03⩆\x05pcup;\x03⩊\x05pdot;\x03⊍\x05rarr;\x03↷\x05rren;\x02¤\x04epr;\x03⋞\x04esc;\x03⋟\x04por;\x03⩅\x04rren\x02¤\x04vee;\x03⋎\x04wed;\x03⋏\x03ps;\x06∪︀\x02p;\x03∪", - // cwconint;[∲] cwint;[∱]. - "\x07conint;\x03∲\x04int;\x03∱", - // cylcty;[⌭]. - "\x05lcty;\x03⌭", - // dArr;[⇓]. - "\x03rr;\x03⇓", - // dHar;[⥥]. - "\x03ar;\x03⥥", - // dagger;[†] daleth;[ℸ] dashv;[⊣] darr;[↓] dash;[‐]. - "\x05gger;\x03†\x05leth;\x03ℸ\x04shv;\x03⊣\x03rr;\x03↓\x03sh;\x03‐", - // dbkarow;[⤏] dblac;[˝]. - "\x06karow;\x03⤏\x04lac;\x02˝", - // dcaron;[ď] dcy;[д]. - "\x05aron;\x02ď\x02y;\x02д", - // ddagger;[‡] ddotseq;[⩷] ddarr;[⇊] dd;[ⅆ]. - "\x06agger;\x03‡\x06otseq;\x03⩷\x04arr;\x03⇊\x01;\x03ⅆ", - // demptyv;[⦱] delta;[δ] deg;[°] deg[°]. - "\x06mptyv;\x03⦱\x04lta;\x02δ\x02g;\x02°\x01g\x02°", - // dfisht;[⥿] dfr;[𝔡]. - "\x05isht;\x03⥿\x02r;\x04𝔡", - // dharl;[⇃] dharr;[⇂]. - "\x04arl;\x03⇃\x04arr;\x03⇂", - // divideontimes;[⋇] diamondsuit;[♦] diamond;[⋄] digamma;[ϝ] divide;[÷] divonx;[⋇] diams;[♦] disin;[⋲] divide[÷] diam;[⋄] die;[¨] div;[÷]. - "\x0cvideontimes;\x03⋇\x0aamondsuit;\x03♦\x06amond;\x03⋄\x06gamma;\x02ϝ\x05vide;\x02÷\x05vonx;\x03⋇\x04ams;\x03♦\x04sin;\x03⋲\x04vide\x02÷\x03am;\x03⋄\x02e;\x02¨\x02v;\x02÷", - // djcy;[ђ]. - "\x03cy;\x02ђ", - // dlcorn;[⌞] dlcrop;[⌍]. - "\x05corn;\x03⌞\x05crop;\x03⌍", - // downharpoonright;[⇂] downharpoonleft;[⇃] doublebarwedge;[⌆] downdownarrows;[⇊] dotsquare;[⊡] downarrow;[↓] doteqdot;[≑] dotminus;[∸] dotplus;[∔] dollar;[$] doteq;[≐] dopf;[𝕕] dot;[˙]. - "\x0fwnharpoonright;\x03⇂\x0ewnharpoonleft;\x03⇃\x0dublebarwedge;\x03⌆\x0dwndownarrows;\x03⇊\x08tsquare;\x03⊡\x08wnarrow;\x03↓\x07teqdot;\x03≑\x07tminus;\x03∸\x06tplus;\x03∔\x05llar;\x01$\x04teq;\x03≐\x03pf;\x04𝕕\x02t;\x02˙", - // drbkarow;[⤐] drcorn;[⌟] drcrop;[⌌]. - "\x07bkarow;\x03⤐\x05corn;\x03⌟\x05crop;\x03⌌", - // dstrok;[đ] dscr;[𝒹] dscy;[ѕ] dsol;[⧶]. - "\x05trok;\x02đ\x03cr;\x04𝒹\x03cy;\x02ѕ\x03ol;\x03⧶", - // dtdot;[⋱] dtrif;[▾] dtri;[▿]. - "\x04dot;\x03⋱\x04rif;\x03▾\x03ri;\x03▿", - // duarr;[⇵] duhar;[⥯]. - "\x04arr;\x03⇵\x04har;\x03⥯", - // dwangle;[⦦]. - "\x06angle;\x03⦦", - // dzigrarr;[⟿] dzcy;[џ]. - "\x07igrarr;\x03⟿\x03cy;\x02џ", - // eDDot;[⩷] eDot;[≑]. - "\x04Dot;\x03⩷\x03ot;\x03≑", - // eacute;[é] easter;[⩮] eacute[é]. - "\x05cute;\x02é\x05ster;\x03⩮\x04cute\x02é", - // ecaron;[ě] ecolon;[≕] ecirc;[ê] ecir;[≖] ecirc[ê] ecy;[э]. - "\x05aron;\x02ě\x05olon;\x03≕\x04irc;\x02ê\x03ir;\x03≖\x03irc\x02ê\x02y;\x02э", - // edot;[ė]. - "\x03ot;\x02ė", - // ee;[ⅇ]. - "\x01;\x03ⅇ", - // efDot;[≒] efr;[𝔢]. - "\x04Dot;\x03≒\x02r;\x04𝔢", - // egrave;[è] egsdot;[⪘] egrave[è] egs;[⪖] eg;[⪚]. - "\x05rave;\x02è\x05sdot;\x03⪘\x04rave\x02è\x02s;\x03⪖\x01;\x03⪚", - // elinters;[⏧] elsdot;[⪗] ell;[ℓ] els;[⪕] el;[⪙]. - "\x07inters;\x03⏧\x05sdot;\x03⪗\x02l;\x03ℓ\x02s;\x03⪕\x01;\x03⪙", - // emptyset;[∅] emptyv;[∅] emsp13;[ ] emsp14;[ ] emacr;[ē] empty;[∅] emsp;[ ]. - "\x07ptyset;\x03∅\x05ptyv;\x03∅\x05sp13;\x03 \x05sp14;\x03 \x04acr;\x02ē\x04pty;\x03∅\x03sp;\x03 ", - // ensp;[ ] eng;[ŋ]. - "\x03sp;\x03 \x02g;\x02ŋ", - // eogon;[ę] eopf;[𝕖]. - "\x04gon;\x02ę\x03pf;\x04𝕖", - // epsilon;[ε] eparsl;[⧣] eplus;[⩱] epsiv;[ϵ] epar;[⋕] epsi;[ε]. - "\x06silon;\x02ε\x05arsl;\x03⧣\x04lus;\x03⩱\x04siv;\x02ϵ\x03ar;\x03⋕\x03si;\x02ε", - // eqslantless;[⪕] eqslantgtr;[⪖] eqvparsl;[⧥] eqcolon;[≕] equivDD;[⩸] eqcirc;[≖] equals;[=] equest;[≟] eqsim;[≂] equiv;[≡]. - "\x0aslantless;\x03⪕\x09slantgtr;\x03⪖\x07vparsl;\x03⧥\x06colon;\x03≕\x06uivDD;\x03⩸\x05circ;\x03≖\x05uals;\x01=\x05uest;\x03≟\x04sim;\x03≂\x04uiv;\x03≡", - // erDot;[≓] erarr;[⥱]. - "\x04Dot;\x03≓\x04arr;\x03⥱", - // esdot;[≐] escr;[ℯ] esim;[≂]. - "\x04dot;\x03≐\x03cr;\x03ℯ\x03im;\x03≂", - // eta;[η] eth;[ð] eth[ð]. - "\x02a;\x02η\x02h;\x02ð\x01h\x02ð", - // euml;[ë] euro;[€] euml[ë]. - "\x03ml;\x02ë\x03ro;\x03€\x02ml\x02ë", - // exponentiale;[ⅇ] expectation;[ℰ] exist;[∃] excl;[!]. - "\x0bponentiale;\x03ⅇ\x0apectation;\x03ℰ\x04ist;\x03∃\x03cl;\x01!", - // fallingdotseq;[≒]. - "\x0cllingdotseq;\x03≒", - // fcy;[ф]. - "\x02y;\x02ф", - // female;[♀]. - "\x05male;\x03♀", - // ffilig;[ffi] ffllig;[ffl] fflig;[ff] ffr;[𝔣]. - "\x05ilig;\x03ffi\x05llig;\x03ffl\x04lig;\x03ff\x02r;\x04𝔣", - // filig;[fi]. - "\x04lig;\x03fi", - // fjlig;[fj]. - "\x04lig;\x02fj", - // fllig;[fl] fltns;[▱] flat;[♭]. - "\x04lig;\x03fl\x04tns;\x03▱\x03at;\x03♭", - // fnof;[ƒ]. - "\x03of;\x02ƒ", - // forall;[∀] forkv;[⫙] fopf;[𝕗] fork;[⋔]. - "\x05rall;\x03∀\x04rkv;\x03⫙\x03pf;\x04𝕗\x03rk;\x03⋔", - // fpartint;[⨍]. - "\x07artint;\x03⨍", - // frac12;[½] frac13;[⅓] frac14;[¼] frac15;[⅕] frac16;[⅙] frac18;[⅛] frac23;[⅔] frac25;[⅖] frac34;[¾] frac35;[⅗] frac38;[⅜] frac45;[⅘] frac56;[⅚] frac58;[⅝] frac78;[⅞] frac12[½] frac14[¼] frac34[¾] frasl;[⁄] frown;[⌢]. - "\x05ac12;\x02½\x05ac13;\x03⅓\x05ac14;\x02¼\x05ac15;\x03⅕\x05ac16;\x03⅙\x05ac18;\x03⅛\x05ac23;\x03⅔\x05ac25;\x03⅖\x05ac34;\x02¾\x05ac35;\x03⅗\x05ac38;\x03⅜\x05ac45;\x03⅘\x05ac56;\x03⅚\x05ac58;\x03⅝\x05ac78;\x03⅞\x04ac12\x02½\x04ac14\x02¼\x04ac34\x02¾\x04asl;\x03⁄\x04own;\x03⌢", - // fscr;[𝒻]. - "\x03cr;\x04𝒻", - // gEl;[⪌] gE;[≧]. - "\x02l;\x03⪌\x01;\x03≧", - // gacute;[ǵ] gammad;[ϝ] gamma;[γ] gap;[⪆]. - "\x05cute;\x02ǵ\x05mmad;\x02ϝ\x04mma;\x02γ\x02p;\x03⪆", - // gbreve;[ğ]. - "\x05reve;\x02ğ", - // gcirc;[ĝ] gcy;[г]. - "\x04irc;\x02ĝ\x02y;\x02г", - // gdot;[ġ]. - "\x03ot;\x02ġ", - // geqslant;[⩾] gesdotol;[⪄] gesdoto;[⪂] gesdot;[⪀] gesles;[⪔] gescc;[⪩] geqq;[≧] gesl;[⋛︀] gel;[⋛] geq;[≥] ges;[⩾] ge;[≥]. - "\x07qslant;\x03⩾\x07sdotol;\x03⪄\x06sdoto;\x03⪂\x05sdot;\x03⪀\x05sles;\x03⪔\x04scc;\x03⪩\x03qq;\x03≧\x03sl;\x06⋛︀\x02l;\x03⋛\x02q;\x03≥\x02s;\x03⩾\x01;\x03≥", - // gfr;[𝔤]. - "\x02r;\x04𝔤", - // ggg;[⋙] gg;[≫]. - "\x02g;\x03⋙\x01;\x03≫", - // gimel;[ℷ]. - "\x04mel;\x03ℷ", - // gjcy;[ѓ]. - "\x03cy;\x02ѓ", - // glE;[⪒] gla;[⪥] glj;[⪤] gl;[≷]. - "\x02E;\x03⪒\x02a;\x03⪥\x02j;\x03⪤\x01;\x03≷", - // gnapprox;[⪊] gneqq;[≩] gnsim;[⋧] gnap;[⪊] gneq;[⪈] gnE;[≩] gne;[⪈]. - "\x07approx;\x03⪊\x04eqq;\x03≩\x04sim;\x03⋧\x03ap;\x03⪊\x03eq;\x03⪈\x02E;\x03≩\x02e;\x03⪈", - // gopf;[𝕘]. - "\x03pf;\x04𝕘", - // grave;[`]. - "\x04ave;\x01`", - // gsime;[⪎] gsiml;[⪐] gscr;[ℊ] gsim;[≳]. - "\x04ime;\x03⪎\x04iml;\x03⪐\x03cr;\x03ℊ\x03im;\x03≳", - // gtreqqless;[⪌] gtrapprox;[⪆] gtreqless;[⋛] gtquest;[⩼] gtrless;[≷] gtlPar;[⦕] gtrarr;[⥸] gtrdot;[⋗] gtrsim;[≳] gtcir;[⩺] gtdot;[⋗] gtcc;[⪧] gt;[>]. - "\x09reqqless;\x03⪌\x08rapprox;\x03⪆\x08reqless;\x03⋛\x06quest;\x03⩼\x06rless;\x03≷\x05lPar;\x03⦕\x05rarr;\x03⥸\x05rdot;\x03⋗\x05rsim;\x03≳\x04cir;\x03⩺\x04dot;\x03⋗\x03cc;\x03⪧\x01;\x01>", - // gvertneqq;[≩︀] gvnE;[≩︀]. - "\x08ertneqq;\x06≩︀\x03nE;\x06≩︀", - // hArr;[⇔]. - "\x03rr;\x03⇔", - // harrcir;[⥈] hairsp;[ ] hamilt;[ℋ] hardcy;[ъ] harrw;[↭] half;[½] harr;[↔]. - "\x06rrcir;\x03⥈\x05irsp;\x03 \x05milt;\x03ℋ\x05rdcy;\x02ъ\x04rrw;\x03↭\x03lf;\x02½\x03rr;\x03↔", - // hbar;[ℏ]. - "\x03ar;\x03ℏ", - // hcirc;[ĥ]. - "\x04irc;\x02ĥ", - // heartsuit;[♥] hearts;[♥] hellip;[…] hercon;[⊹]. - "\x08artsuit;\x03♥\x05arts;\x03♥\x05llip;\x03…\x05rcon;\x03⊹", - // hfr;[𝔥]. - "\x02r;\x04𝔥", - // hksearow;[⤥] hkswarow;[⤦]. - "\x07searow;\x03⤥\x07swarow;\x03⤦", - // hookrightarrow;[↪] hookleftarrow;[↩] homtht;[∻] horbar;[―] hoarr;[⇿] hopf;[𝕙]. - "\x0dokrightarrow;\x03↪\x0cokleftarrow;\x03↩\x05mtht;\x03∻\x05rbar;\x03―\x04arr;\x03⇿\x03pf;\x04𝕙", - // hslash;[ℏ] hstrok;[ħ] hscr;[𝒽]. - "\x05lash;\x03ℏ\x05trok;\x02ħ\x03cr;\x04𝒽", - // hybull;[⁃] hyphen;[‐]. - "\x05bull;\x03⁃\x05phen;\x03‐", - // iacute;[í] iacute[í]. - "\x05cute;\x02í\x04cute\x02í", - // icirc;[î] icirc[î] icy;[и] ic;[⁣]. - "\x04irc;\x02î\x03irc\x02î\x02y;\x02и\x01;\x03⁣", - // iexcl;[¡] iecy;[е] iexcl[¡]. - "\x04xcl;\x02¡\x03cy;\x02е\x03xcl\x02¡", - // iff;[⇔] ifr;[𝔦]. - "\x02f;\x03⇔\x02r;\x04𝔦", - // igrave;[ì] igrave[ì]. - "\x05rave;\x02ì\x04rave\x02ì", - // iiiint;[⨌] iinfin;[⧜] iiint;[∭] iiota;[℩] ii;[ⅈ]. - "\x05iint;\x03⨌\x05nfin;\x03⧜\x04int;\x03∭\x04ota;\x03℩\x01;\x03ⅈ", - // ijlig;[ij]. - "\x04lig;\x02ij", - // imagline;[ℐ] imagpart;[ℑ] imacr;[ī] image;[ℑ] imath;[ı] imped;[Ƶ] imof;[⊷]. - "\x07agline;\x03ℐ\x07agpart;\x03ℑ\x04acr;\x02ī\x04age;\x03ℑ\x04ath;\x02ı\x04ped;\x02Ƶ\x03of;\x03⊷", - // infintie;[⧝] integers;[ℤ] intercal;[⊺] intlarhk;[⨗] intprod;[⨼] incare;[℅] inodot;[ı] intcal;[⊺] infin;[∞] int;[∫] in;[∈]. - "\x07fintie;\x03⧝\x07tegers;\x03ℤ\x07tercal;\x03⊺\x07tlarhk;\x03⨗\x06tprod;\x03⨼\x05care;\x03℅\x05odot;\x02ı\x05tcal;\x03⊺\x04fin;\x03∞\x02t;\x03∫\x01;\x03∈", - // iogon;[į] iocy;[ё] iopf;[𝕚] iota;[ι]. - "\x04gon;\x02į\x03cy;\x02ё\x03pf;\x04𝕚\x03ta;\x02ι", - // iprod;[⨼]. - "\x04rod;\x03⨼", - // iquest;[¿] iquest[¿]. - "\x05uest;\x02¿\x04uest\x02¿", - // isindot;[⋵] isinsv;[⋳] isinE;[⋹] isins;[⋴] isinv;[∈] iscr;[𝒾] isin;[∈]. - "\x06indot;\x03⋵\x05insv;\x03⋳\x04inE;\x03⋹\x04ins;\x03⋴\x04inv;\x03∈\x03cr;\x04𝒾\x03in;\x03∈", - // itilde;[ĩ] it;[⁢]. - "\x05ilde;\x02ĩ\x01;\x03⁢", - // iukcy;[і] iuml;[ï] iuml[ï]. - "\x04kcy;\x02і\x03ml;\x02ï\x02ml\x02ï", - // jcirc;[ĵ] jcy;[й]. - "\x04irc;\x02ĵ\x02y;\x02й", - // jfr;[𝔧]. - "\x02r;\x04𝔧", - // jmath;[ȷ]. - "\x04ath;\x02ȷ", - // jopf;[𝕛]. - "\x03pf;\x04𝕛", - // jsercy;[ј] jscr;[𝒿]. - "\x05ercy;\x02ј\x03cr;\x04𝒿", - // jukcy;[є]. - "\x04kcy;\x02є", - // kappav;[ϰ] kappa;[κ]. - "\x05ppav;\x02ϰ\x04ppa;\x02κ", - // kcedil;[ķ] kcy;[к]. - "\x05edil;\x02ķ\x02y;\x02к", - // kfr;[𝔨]. - "\x02r;\x04𝔨", - // kgreen;[ĸ]. - "\x05reen;\x02ĸ", - // khcy;[х]. - "\x03cy;\x02х", - // kjcy;[ќ]. - "\x03cy;\x02ќ", - // kopf;[𝕜]. - "\x03pf;\x04𝕜", - // kscr;[𝓀]. - "\x03cr;\x04𝓀", - // lAtail;[⤛] lAarr;[⇚] lArr;[⇐]. - "\x05tail;\x03⤛\x04arr;\x03⇚\x03rr;\x03⇐", - // lBarr;[⤎]. - "\x04arr;\x03⤎", - // lEg;[⪋] lE;[≦]. - "\x02g;\x03⪋\x01;\x03≦", - // lHar;[⥢]. - "\x03ar;\x03⥢", - // laemptyv;[⦴] larrbfs;[⤟] larrsim;[⥳] lacute;[ĺ] lagran;[ℒ] lambda;[λ] langle;[⟨] larrfs;[⤝] larrhk;[↩] larrlp;[↫] larrpl;[⤹] larrtl;[↢] latail;[⤙] langd;[⦑] laquo;[«] larrb;[⇤] lates;[⪭︀] lang;[⟨] laquo[«] larr;[←] late;[⪭] lap;[⪅] lat;[⪫]. - "\x07emptyv;\x03⦴\x06rrbfs;\x03⤟\x06rrsim;\x03⥳\x05cute;\x02ĺ\x05gran;\x03ℒ\x05mbda;\x02λ\x05ngle;\x03⟨\x05rrfs;\x03⤝\x05rrhk;\x03↩\x05rrlp;\x03↫\x05rrpl;\x03⤹\x05rrtl;\x03↢\x05tail;\x03⤙\x04ngd;\x03⦑\x04quo;\x02«\x04rrb;\x03⇤\x04tes;\x06⪭︀\x03ng;\x03⟨\x03quo\x02«\x03rr;\x03←\x03te;\x03⪭\x02p;\x03⪅\x02t;\x03⪫", - // lbrksld;[⦏] lbrkslu;[⦍] lbrace;[{] lbrack;[[] lbarr;[⤌] lbbrk;[❲] lbrke;[⦋]. - "\x06rksld;\x03⦏\x06rkslu;\x03⦍\x05race;\x01{\x05rack;\x01[\x04arr;\x03⤌\x04brk;\x03❲\x04rke;\x03⦋", - // lcaron;[ľ] lcedil;[ļ] lceil;[⌈] lcub;[{] lcy;[л]. - "\x05aron;\x02ľ\x05edil;\x02ļ\x04eil;\x03⌈\x03ub;\x01{\x02y;\x02л", - // ldrushar;[⥋] ldrdhar;[⥧] ldquor;[„] ldquo;[“] ldca;[⤶] ldsh;[↲]. - "\x07rushar;\x03⥋\x06rdhar;\x03⥧\x05quor;\x03„\x04quo;\x03“\x03ca;\x03⤶\x03sh;\x03↲", - // leftrightsquigarrow;[↭] leftrightharpoons;[⇋] leftharpoondown;[↽] leftrightarrows;[⇆] leftleftarrows;[⇇] leftrightarrow;[↔] leftthreetimes;[⋋] leftarrowtail;[↢] leftharpoonup;[↼] lessapprox;[⪅] lesseqqgtr;[⪋] leftarrow;[←] lesseqgtr;[⋚] leqslant;[⩽] lesdotor;[⪃] lesdoto;[⪁] lessdot;[⋖] lessgtr;[≶] lesssim;[≲] lesdot;[⩿] lesges;[⪓] lescc;[⪨] leqq;[≦] lesg;[⋚︀] leg;[⋚] leq;[≤] les;[⩽] le;[≤]. - "\x12ftrightsquigarrow;\x03↭\x10ftrightharpoons;\x03⇋\x0eftharpoondown;\x03↽\x0eftrightarrows;\x03⇆\x0dftleftarrows;\x03⇇\x0dftrightarrow;\x03↔\x0dftthreetimes;\x03⋋\x0cftarrowtail;\x03↢\x0cftharpoonup;\x03↼\x09ssapprox;\x03⪅\x09sseqqgtr;\x03⪋\x08ftarrow;\x03←\x08sseqgtr;\x03⋚\x07qslant;\x03⩽\x07sdotor;\x03⪃\x06sdoto;\x03⪁\x06ssdot;\x03⋖\x06ssgtr;\x03≶\x06sssim;\x03≲\x05sdot;\x03⩿\x05sges;\x03⪓\x04scc;\x03⪨\x03qq;\x03≦\x03sg;\x06⋚︀\x02g;\x03⋚\x02q;\x03≤\x02s;\x03⩽\x01;\x03≤", - // lfisht;[⥼] lfloor;[⌊] lfr;[𝔩]. - "\x05isht;\x03⥼\x05loor;\x03⌊\x02r;\x04𝔩", - // lgE;[⪑] lg;[≶]. - "\x02E;\x03⪑\x01;\x03≶", - // lharul;[⥪] lhard;[↽] lharu;[↼] lhblk;[▄]. - "\x05arul;\x03⥪\x04ard;\x03↽\x04aru;\x03↼\x04blk;\x03▄", - // ljcy;[љ]. - "\x03cy;\x02љ", - // llcorner;[⌞] llhard;[⥫] llarr;[⇇] lltri;[◺] ll;[≪]. - "\x07corner;\x03⌞\x05hard;\x03⥫\x04arr;\x03⇇\x04tri;\x03◺\x01;\x03≪", - // lmoustache;[⎰] lmidot;[ŀ] lmoust;[⎰]. - "\x09oustache;\x03⎰\x05idot;\x02ŀ\x05oust;\x03⎰", - // lnapprox;[⪉] lneqq;[≨] lnsim;[⋦] lnap;[⪉] lneq;[⪇] lnE;[≨] lne;[⪇]. - "\x07approx;\x03⪉\x04eqq;\x03≨\x04sim;\x03⋦\x03ap;\x03⪉\x03eq;\x03⪇\x02E;\x03≨\x02e;\x03⪇", - // longleftrightarrow;[⟷] longrightarrow;[⟶] looparrowright;[↬] longleftarrow;[⟵] looparrowleft;[↫] longmapsto;[⟼] lotimes;[⨴] lozenge;[◊] loplus;[⨭] lowast;[∗] lowbar;[_] loang;[⟬] loarr;[⇽] lobrk;[⟦] lopar;[⦅] lopf;[𝕝] lozf;[⧫] loz;[◊]. - "\x11ngleftrightarrow;\x03⟷\x0dngrightarrow;\x03⟶\x0doparrowright;\x03↬\x0cngleftarrow;\x03⟵\x0coparrowleft;\x03↫\x09ngmapsto;\x03⟼\x06times;\x03⨴\x06zenge;\x03◊\x05plus;\x03⨭\x05wast;\x03∗\x05wbar;\x01_\x04ang;\x03⟬\x04arr;\x03⇽\x04brk;\x03⟦\x04par;\x03⦅\x03pf;\x04𝕝\x03zf;\x03⧫\x02z;\x03◊", - // lparlt;[⦓] lpar;[(]. - "\x05arlt;\x03⦓\x03ar;\x01(", - // lrcorner;[⌟] lrhard;[⥭] lrarr;[⇆] lrhar;[⇋] lrtri;[⊿] lrm;[‎]. - "\x07corner;\x03⌟\x05hard;\x03⥭\x04arr;\x03⇆\x04har;\x03⇋\x04tri;\x03⊿\x02m;\x03‎", - // lsaquo;[‹] lsquor;[‚] lstrok;[ł] lsime;[⪍] lsimg;[⪏] lsquo;[‘] lscr;[𝓁] lsim;[≲] lsqb;[[] lsh;[↰]. - "\x05aquo;\x03‹\x05quor;\x03‚\x05trok;\x02ł\x04ime;\x03⪍\x04img;\x03⪏\x04quo;\x03‘\x03cr;\x04𝓁\x03im;\x03≲\x03qb;\x01[\x02h;\x03↰", - // ltquest;[⩻] lthree;[⋋] ltimes;[⋉] ltlarr;[⥶] ltrPar;[⦖] ltcir;[⩹] ltdot;[⋖] ltrie;[⊴] ltrif;[◂] ltcc;[⪦] ltri;[◃] lt;[<]. - "\x06quest;\x03⩻\x05hree;\x03⋋\x05imes;\x03⋉\x05larr;\x03⥶\x05rPar;\x03⦖\x04cir;\x03⩹\x04dot;\x03⋖\x04rie;\x03⊴\x04rif;\x03◂\x03cc;\x03⪦\x03ri;\x03◃\x01;\x01<", - // lurdshar;[⥊] luruhar;[⥦]. - "\x07rdshar;\x03⥊\x06ruhar;\x03⥦", - // lvertneqq;[≨︀] lvnE;[≨︀]. - "\x08ertneqq;\x06≨︀\x03nE;\x06≨︀", - // mDDot;[∺]. - "\x04Dot;\x03∺", - // mapstodown;[↧] mapstoleft;[↤] mapstoup;[↥] maltese;[✠] mapsto;[↦] marker;[▮] macr;[¯] male;[♂] malt;[✠] macr[¯] map;[↦]. - "\x09pstodown;\x03↧\x09pstoleft;\x03↤\x07pstoup;\x03↥\x06ltese;\x03✠\x05psto;\x03↦\x05rker;\x03▮\x03cr;\x02¯\x03le;\x03♂\x03lt;\x03✠\x02cr\x02¯\x02p;\x03↦", - // mcomma;[⨩] mcy;[м]. - "\x05omma;\x03⨩\x02y;\x02м", - // mdash;[—]. - "\x04ash;\x03—", - // measuredangle;[∡]. - "\x0casuredangle;\x03∡", - // mfr;[𝔪]. - "\x02r;\x04𝔪", - // mho;[℧]. - "\x02o;\x03℧", - // minusdu;[⨪] midast;[*] midcir;[⫰] middot;[·] minusb;[⊟] minusd;[∸] micro;[µ] middot[·] minus;[−] micro[µ] mid;[∣]. - "\x06nusdu;\x03⨪\x05dast;\x01*\x05dcir;\x03⫰\x05ddot;\x02·\x05nusb;\x03⊟\x05nusd;\x03∸\x04cro;\x02µ\x04ddot\x02·\x04nus;\x03−\x03cro\x02µ\x02d;\x03∣", - // mlcp;[⫛] mldr;[…]. - "\x03cp;\x03⫛\x03dr;\x03…", - // mnplus;[∓]. - "\x05plus;\x03∓", - // models;[⊧] mopf;[𝕞]. - "\x05dels;\x03⊧\x03pf;\x04𝕞", - // mp;[∓]. - "\x01;\x03∓", - // mstpos;[∾] mscr;[𝓂]. - "\x05tpos;\x03∾\x03cr;\x04𝓂", - // multimap;[⊸] mumap;[⊸] mu;[μ]. - "\x07ltimap;\x03⊸\x04map;\x03⊸\x01;\x02μ", - // nGtv;[≫̸] nGg;[⋙̸] nGt;[≫⃒]. - "\x03tv;\x05≫̸\x02g;\x05⋙̸\x02t;\x06≫⃒", - // nLeftrightarrow;[⇎] nLeftarrow;[⇍] nLtv;[≪̸] nLl;[⋘̸] nLt;[≪⃒]. - "\x0eeftrightarrow;\x03⇎\x09eftarrow;\x03⇍\x03tv;\x05≪̸\x02l;\x05⋘̸\x02t;\x06≪⃒", - // nRightarrow;[⇏]. - "\x0aightarrow;\x03⇏", - // nVDash;[⊯] nVdash;[⊮]. - "\x05Dash;\x03⊯\x05dash;\x03⊮", - // naturals;[ℕ] napprox;[≉] natural;[♮] nacute;[ń] nabla;[∇] napid;[≋̸] napos;[ʼn] natur;[♮] nang;[∠⃒] napE;[⩰̸] nap;[≉]. - "\x07turals;\x03ℕ\x06pprox;\x03≉\x06tural;\x03♮\x05cute;\x02ń\x04bla;\x03∇\x04pid;\x05≋̸\x04pos;\x02ʼn\x04tur;\x03♮\x03ng;\x06∠⃒\x03pE;\x05⩰̸\x02p;\x03≉", - // nbumpe;[≏̸] nbump;[≎̸] nbsp;[ ] nbsp[ ]. - "\x05umpe;\x05≏̸\x04ump;\x05≎̸\x03sp;\x02 \x02sp\x02 ", - // ncongdot;[⩭̸] ncaron;[ň] ncedil;[ņ] ncong;[≇] ncap;[⩃] ncup;[⩂] ncy;[н]. - "\x07ongdot;\x05⩭̸\x05aron;\x02ň\x05edil;\x02ņ\x04ong;\x03≇\x03ap;\x03⩃\x03up;\x03⩂\x02y;\x02н", - // ndash;[–]. - "\x04ash;\x03–", - // nearrow;[↗] nexists;[∄] nearhk;[⤤] nequiv;[≢] nesear;[⤨] nexist;[∄] neArr;[⇗] nearr;[↗] nedot;[≐̸] nesim;[≂̸] ne;[≠]. - "\x06arrow;\x03↗\x06xists;\x03∄\x05arhk;\x03⤤\x05quiv;\x03≢\x05sear;\x03⤨\x05xist;\x03∄\x04Arr;\x03⇗\x04arr;\x03↗\x04dot;\x05≐̸\x04sim;\x05≂̸\x01;\x03≠", - // nfr;[𝔫]. - "\x02r;\x04𝔫", - // ngeqslant;[⩾̸] ngeqq;[≧̸] ngsim;[≵] ngeq;[≱] nges;[⩾̸] ngtr;[≯] ngE;[≧̸] nge;[≱] ngt;[≯]. - "\x08eqslant;\x05⩾̸\x04eqq;\x05≧̸\x04sim;\x03≵\x03eq;\x03≱\x03es;\x05⩾̸\x03tr;\x03≯\x02E;\x05≧̸\x02e;\x03≱\x02t;\x03≯", - // nhArr;[⇎] nharr;[↮] nhpar;[⫲]. - "\x04Arr;\x03⇎\x04arr;\x03↮\x04par;\x03⫲", - // nisd;[⋺] nis;[⋼] niv;[∋] ni;[∋]. - "\x03sd;\x03⋺\x02s;\x03⋼\x02v;\x03∋\x01;\x03∋", - // njcy;[њ]. - "\x03cy;\x02њ", - // nleftrightarrow;[↮] nleftarrow;[↚] nleqslant;[⩽̸] nltrie;[⋬] nlArr;[⇍] nlarr;[↚] nleqq;[≦̸] nless;[≮] nlsim;[≴] nltri;[⋪] nldr;[‥] nleq;[≰] nles;[⩽̸] nlE;[≦̸] nle;[≰] nlt;[≮]. - "\x0eeftrightarrow;\x03↮\x09eftarrow;\x03↚\x08eqslant;\x05⩽̸\x05trie;\x03⋬\x04Arr;\x03⇍\x04arr;\x03↚\x04eqq;\x05≦̸\x04ess;\x03≮\x04sim;\x03≴\x04tri;\x03⋪\x03dr;\x03‥\x03eq;\x03≰\x03es;\x05⩽̸\x02E;\x05≦̸\x02e;\x03≰\x02t;\x03≮", - // nmid;[∤]. - "\x03id;\x03∤", - // notindot;[⋵̸] notinva;[∉] notinvb;[⋷] notinvc;[⋶] notniva;[∌] notnivb;[⋾] notnivc;[⋽] notinE;[⋹̸] notin;[∉] notni;[∌] nopf;[𝕟] not;[¬] not[¬]. - "\x07tindot;\x05⋵̸\x06tinva;\x03∉\x06tinvb;\x03⋷\x06tinvc;\x03⋶\x06tniva;\x03∌\x06tnivb;\x03⋾\x06tnivc;\x03⋽\x05tinE;\x05⋹̸\x04tin;\x03∉\x04tni;\x03∌\x03pf;\x04𝕟\x02t;\x02¬\x01t\x02¬", - // nparallel;[∦] npolint;[⨔] npreceq;[⪯̸] nparsl;[⫽⃥] nprcue;[⋠] npart;[∂̸] nprec;[⊀] npar;[∦] npre;[⪯̸] npr;[⊀]. - "\x08arallel;\x03∦\x06olint;\x03⨔\x06receq;\x05⪯̸\x05arsl;\x06⫽⃥\x05rcue;\x03⋠\x04art;\x05∂̸\x04rec;\x03⊀\x03ar;\x03∦\x03re;\x05⪯̸\x02r;\x03⊀", - // nrightarrow;[↛] nrarrc;[⤳̸] nrarrw;[↝̸] nrtrie;[⋭] nrArr;[⇏] nrarr;[↛] nrtri;[⋫]. - "\x0aightarrow;\x03↛\x05arrc;\x05⤳̸\x05arrw;\x05↝̸\x05trie;\x03⋭\x04Arr;\x03⇏\x04arr;\x03↛\x04tri;\x03⋫", - // nshortparallel;[∦] nsubseteqq;[⫅̸] nsupseteqq;[⫆̸] nshortmid;[∤] nsubseteq;[⊈] nsupseteq;[⊉] nsqsube;[⋢] nsqsupe;[⋣] nsubset;[⊂⃒] nsucceq;[⪰̸] nsupset;[⊃⃒] nsccue;[⋡] nsimeq;[≄] nsime;[≄] nsmid;[∤] nspar;[∦] nsubE;[⫅̸] nsube;[⊈] nsucc;[⊁] nsupE;[⫆̸] nsupe;[⊉] nsce;[⪰̸] nscr;[𝓃] nsim;[≁] nsub;[⊄] nsup;[⊅] nsc;[⊁]. - "\x0dhortparallel;\x03∦\x09ubseteqq;\x05⫅̸\x09upseteqq;\x05⫆̸\x08hortmid;\x03∤\x08ubseteq;\x03⊈\x08upseteq;\x03⊉\x06qsube;\x03⋢\x06qsupe;\x03⋣\x06ubset;\x06⊂⃒\x06ucceq;\x05⪰̸\x06upset;\x06⊃⃒\x05ccue;\x03⋡\x05imeq;\x03≄\x04ime;\x03≄\x04mid;\x03∤\x04par;\x03∦\x04ubE;\x05⫅̸\x04ube;\x03⊈\x04ucc;\x03⊁\x04upE;\x05⫆̸\x04upe;\x03⊉\x03ce;\x05⪰̸\x03cr;\x04𝓃\x03im;\x03≁\x03ub;\x03⊄\x03up;\x03⊅\x02c;\x03⊁", - // ntrianglerighteq;[⋭] ntrianglelefteq;[⋬] ntriangleright;[⋫] ntriangleleft;[⋪] ntilde;[ñ] ntilde[ñ] ntgl;[≹] ntlg;[≸]. - "\x0frianglerighteq;\x03⋭\x0erianglelefteq;\x03⋬\x0driangleright;\x03⋫\x0criangleleft;\x03⋪\x05ilde;\x02ñ\x04ilde\x02ñ\x03gl;\x03≹\x03lg;\x03≸", - // numero;[№] numsp;[ ] num;[#] nu;[ν]. - "\x05mero;\x03№\x04msp;\x03 \x02m;\x01#\x01;\x02ν", - // nvinfin;[⧞] nvltrie;[⊴⃒] nvrtrie;[⊵⃒] nvDash;[⊭] nvHarr;[⤄] nvdash;[⊬] nvlArr;[⤂] nvrArr;[⤃] nvsim;[∼⃒] nvap;[≍⃒] nvge;[≥⃒] nvgt;[>⃒] nvle;[≤⃒] nvlt;[<⃒]. - "\x06infin;\x03⧞\x06ltrie;\x06⊴⃒\x06rtrie;\x06⊵⃒\x05Dash;\x03⊭\x05Harr;\x03⤄\x05dash;\x03⊬\x05lArr;\x03⤂\x05rArr;\x03⤃\x04sim;\x06∼⃒\x03ap;\x06≍⃒\x03ge;\x06≥⃒\x03gt;\x04>⃒\x03le;\x06≤⃒\x03lt;\x04<⃒", - // nwarrow;[↖] nwarhk;[⤣] nwnear;[⤧] nwArr;[⇖] nwarr;[↖]. - "\x06arrow;\x03↖\x05arhk;\x03⤣\x05near;\x03⤧\x04Arr;\x03⇖\x04arr;\x03↖", - // oS;[Ⓢ]. - "\x01;\x03Ⓢ", - // oacute;[ó] oacute[ó] oast;[⊛]. - "\x05cute;\x02ó\x04cute\x02ó\x03st;\x03⊛", - // ocirc;[ô] ocir;[⊚] ocirc[ô] ocy;[о]. - "\x04irc;\x02ô\x03ir;\x03⊚\x03irc\x02ô\x02y;\x02о", - // odblac;[ő] odsold;[⦼] odash;[⊝] odiv;[⨸] odot;[⊙]. - "\x05blac;\x02ő\x05sold;\x03⦼\x04ash;\x03⊝\x03iv;\x03⨸\x03ot;\x03⊙", - // oelig;[œ]. - "\x04lig;\x02œ", - // ofcir;[⦿] ofr;[𝔬]. - "\x04cir;\x03⦿\x02r;\x04𝔬", - // ograve;[ò] ograve[ò] ogon;[˛] ogt;[⧁]. - "\x05rave;\x02ò\x04rave\x02ò\x03on;\x02˛\x02t;\x03⧁", - // ohbar;[⦵] ohm;[Ω]. - "\x04bar;\x03⦵\x02m;\x02Ω", - // oint;[∮]. - "\x03nt;\x03∮", - // olcross;[⦻] olarr;[↺] olcir;[⦾] oline;[‾] olt;[⧀]. - "\x06cross;\x03⦻\x04arr;\x03↺\x04cir;\x03⦾\x04ine;\x03‾\x02t;\x03⧀", - // omicron;[ο] ominus;[⊖] omacr;[ō] omega;[ω] omid;[⦶]. - "\x06icron;\x02ο\x05inus;\x03⊖\x04acr;\x02ō\x04ega;\x02ω\x03id;\x03⦶", - // oopf;[𝕠]. - "\x03pf;\x04𝕠", - // operp;[⦹] oplus;[⊕] opar;[⦷]. - "\x04erp;\x03⦹\x04lus;\x03⊕\x03ar;\x03⦷", - // orderof;[ℴ] orslope;[⩗] origof;[⊶] orarr;[↻] order;[ℴ] ordf;[ª] ordm;[º] oror;[⩖] ord;[⩝] ordf[ª] ordm[º] orv;[⩛] or;[∨]. - "\x06derof;\x03ℴ\x06slope;\x03⩗\x05igof;\x03⊶\x04arr;\x03↻\x04der;\x03ℴ\x03df;\x02ª\x03dm;\x02º\x03or;\x03⩖\x02d;\x03⩝\x02df\x02ª\x02dm\x02º\x02v;\x03⩛\x01;\x03∨", - // oslash;[ø] oslash[ø] oscr;[ℴ] osol;[⊘]. - "\x05lash;\x02ø\x04lash\x02ø\x03cr;\x03ℴ\x03ol;\x03⊘", - // otimesas;[⨶] otilde;[õ] otimes;[⊗] otilde[õ]. - "\x07imesas;\x03⨶\x05ilde;\x02õ\x05imes;\x03⊗\x04ilde\x02õ", - // ouml;[ö] ouml[ö]. - "\x03ml;\x02ö\x02ml\x02ö", - // ovbar;[⌽]. - "\x04bar;\x03⌽", - // parallel;[∥] parsim;[⫳] parsl;[⫽] para;[¶] part;[∂] par;[∥] para[¶]. - "\x07rallel;\x03∥\x05rsim;\x03⫳\x04rsl;\x03⫽\x03ra;\x02¶\x03rt;\x03∂\x02r;\x03∥\x02ra\x02¶", - // pcy;[п]. - "\x02y;\x02п", - // pertenk;[‱] percnt;[%] period;[.] permil;[‰] perp;[⊥]. - "\x06rtenk;\x03‱\x05rcnt;\x01%\x05riod;\x01.\x05rmil;\x03‰\x03rp;\x03⊥", - // pfr;[𝔭]. - "\x02r;\x04𝔭", - // phmmat;[ℳ] phone;[☎] phiv;[ϕ] phi;[φ]. - "\x05mmat;\x03ℳ\x04one;\x03☎\x03iv;\x02ϕ\x02i;\x02φ", - // pitchfork;[⋔] piv;[ϖ] pi;[π]. - "\x08tchfork;\x03⋔\x02v;\x02ϖ\x01;\x02π", - // plusacir;[⨣] planckh;[ℎ] pluscir;[⨢] plussim;[⨦] plustwo;[⨧] planck;[ℏ] plankv;[ℏ] plusdo;[∔] plusdu;[⨥] plusmn;[±] plusb;[⊞] pluse;[⩲] plusmn[±] plus;[+]. - "\x07usacir;\x03⨣\x06anckh;\x03ℎ\x06uscir;\x03⨢\x06ussim;\x03⨦\x06ustwo;\x03⨧\x05anck;\x03ℏ\x05ankv;\x03ℏ\x05usdo;\x03∔\x05usdu;\x03⨥\x05usmn;\x02±\x04usb;\x03⊞\x04use;\x03⩲\x04usmn\x02±\x03us;\x01+", - // pm;[±]. - "\x01;\x02±", - // pointint;[⨕] pound;[£] popf;[𝕡] pound[£]. - "\x07intint;\x03⨕\x04und;\x02£\x03pf;\x04𝕡\x03und\x02£", - // preccurlyeq;[≼] precnapprox;[⪹] precapprox;[⪷] precneqq;[⪵] precnsim;[⋨] profalar;[⌮] profline;[⌒] profsurf;[⌓] precsim;[≾] preceq;[⪯] primes;[ℙ] prnsim;[⋨] propto;[∝] prurel;[⊰] prcue;[≼] prime;[′] prnap;[⪹] prsim;[≾] prap;[⪷] prec;[≺] prnE;[⪵] prod;[∏] prop;[∝] prE;[⪳] pre;[⪯] pr;[≺]. - "\x0aeccurlyeq;\x03≼\x0aecnapprox;\x03⪹\x09ecapprox;\x03⪷\x07ecneqq;\x03⪵\x07ecnsim;\x03⋨\x07ofalar;\x03⌮\x07ofline;\x03⌒\x07ofsurf;\x03⌓\x06ecsim;\x03≾\x05eceq;\x03⪯\x05imes;\x03ℙ\x05nsim;\x03⋨\x05opto;\x03∝\x05urel;\x03⊰\x04cue;\x03≼\x04ime;\x03′\x04nap;\x03⪹\x04sim;\x03≾\x03ap;\x03⪷\x03ec;\x03≺\x03nE;\x03⪵\x03od;\x03∏\x03op;\x03∝\x02E;\x03⪳\x02e;\x03⪯\x01;\x03≺", - // pscr;[𝓅] psi;[ψ]. - "\x03cr;\x04𝓅\x02i;\x02ψ", - // puncsp;[ ]. - "\x05ncsp;\x03 ", - // qfr;[𝔮]. - "\x02r;\x04𝔮", - // qint;[⨌]. - "\x03nt;\x03⨌", - // qopf;[𝕢]. - "\x03pf;\x04𝕢", - // qprime;[⁗]. - "\x05rime;\x03⁗", - // qscr;[𝓆]. - "\x03cr;\x04𝓆", - // quaternions;[ℍ] quatint;[⨖] questeq;[≟] quest;[?] quot;[\"] quot[\"]. - "\x0aaternions;\x03ℍ\x06atint;\x03⨖\x06esteq;\x03≟\x04est;\x01?\x03ot;\x01\"\x02ot\x01\"", - // rAtail;[⤜] rAarr;[⇛] rArr;[⇒]. - "\x05tail;\x03⤜\x04arr;\x03⇛\x03rr;\x03⇒", - // rBarr;[⤏]. - "\x04arr;\x03⤏", - // rHar;[⥤]. - "\x03ar;\x03⥤", - // rationals;[ℚ] raemptyv;[⦳] rarrbfs;[⤠] rarrsim;[⥴] racute;[ŕ] rangle;[⟩] rarrap;[⥵] rarrfs;[⤞] rarrhk;[↪] rarrlp;[↬] rarrpl;[⥅] rarrtl;[↣] ratail;[⤚] radic;[√] rangd;[⦒] range;[⦥] raquo;[»] rarrb;[⇥] rarrc;[⤳] rarrw;[↝] ratio;[∶] race;[∽̱] rang;[⟩] raquo[»] rarr;[→]. - "\x08tionals;\x03ℚ\x07emptyv;\x03⦳\x06rrbfs;\x03⤠\x06rrsim;\x03⥴\x05cute;\x02ŕ\x05ngle;\x03⟩\x05rrap;\x03⥵\x05rrfs;\x03⤞\x05rrhk;\x03↪\x05rrlp;\x03↬\x05rrpl;\x03⥅\x05rrtl;\x03↣\x05tail;\x03⤚\x04dic;\x03√\x04ngd;\x03⦒\x04nge;\x03⦥\x04quo;\x02»\x04rrb;\x03⇥\x04rrc;\x03⤳\x04rrw;\x03↝\x04tio;\x03∶\x03ce;\x05∽̱\x03ng;\x03⟩\x03quo\x02»\x03rr;\x03→", - // rbrksld;[⦎] rbrkslu;[⦐] rbrace;[}] rbrack;[]] rbarr;[⤍] rbbrk;[❳] rbrke;[⦌]. - "\x06rksld;\x03⦎\x06rkslu;\x03⦐\x05race;\x01}\x05rack;\x01]\x04arr;\x03⤍\x04brk;\x03❳\x04rke;\x03⦌", - // rcaron;[ř] rcedil;[ŗ] rceil;[⌉] rcub;[}] rcy;[р]. - "\x05aron;\x02ř\x05edil;\x02ŗ\x04eil;\x03⌉\x03ub;\x01}\x02y;\x02р", - // rdldhar;[⥩] rdquor;[”] rdquo;[”] rdca;[⤷] rdsh;[↳]. - "\x06ldhar;\x03⥩\x05quor;\x03”\x04quo;\x03”\x03ca;\x03⤷\x03sh;\x03↳", - // realpart;[ℜ] realine;[ℛ] reals;[ℝ] real;[ℜ] rect;[▭] reg;[®] reg[®]. - "\x07alpart;\x03ℜ\x06aline;\x03ℛ\x04als;\x03ℝ\x03al;\x03ℜ\x03ct;\x03▭\x02g;\x02®\x01g\x02®", - // rfisht;[⥽] rfloor;[⌋] rfr;[𝔯]. - "\x05isht;\x03⥽\x05loor;\x03⌋\x02r;\x04𝔯", - // rharul;[⥬] rhard;[⇁] rharu;[⇀] rhov;[ϱ] rho;[ρ]. - "\x05arul;\x03⥬\x04ard;\x03⇁\x04aru;\x03⇀\x03ov;\x02ϱ\x02o;\x02ρ", - // rightleftharpoons;[⇌] rightharpoondown;[⇁] rightrightarrows;[⇉] rightleftarrows;[⇄] rightsquigarrow;[↝] rightthreetimes;[⋌] rightarrowtail;[↣] rightharpoonup;[⇀] risingdotseq;[≓] rightarrow;[→] ring;[˚]. - "\x10ghtleftharpoons;\x03⇌\x0fghtharpoondown;\x03⇁\x0fghtrightarrows;\x03⇉\x0eghtleftarrows;\x03⇄\x0eghtsquigarrow;\x03↝\x0eghtthreetimes;\x03⋌\x0dghtarrowtail;\x03↣\x0dghtharpoonup;\x03⇀\x0bsingdotseq;\x03≓\x09ghtarrow;\x03→\x03ng;\x02˚", - // rlarr;[⇄] rlhar;[⇌] rlm;[‏]. - "\x04arr;\x03⇄\x04har;\x03⇌\x02m;\x03‏", - // rmoustache;[⎱] rmoust;[⎱]. - "\x09oustache;\x03⎱\x05oust;\x03⎱", - // rnmid;[⫮]. - "\x04mid;\x03⫮", - // rotimes;[⨵] roplus;[⨮] roang;[⟭] roarr;[⇾] robrk;[⟧] ropar;[⦆] ropf;[𝕣]. - "\x06times;\x03⨵\x05plus;\x03⨮\x04ang;\x03⟭\x04arr;\x03⇾\x04brk;\x03⟧\x04par;\x03⦆\x03pf;\x04𝕣", - // rppolint;[⨒] rpargt;[⦔] rpar;[)]. - "\x07polint;\x03⨒\x05argt;\x03⦔\x03ar;\x01)", - // rrarr;[⇉]. - "\x04arr;\x03⇉", - // rsaquo;[›] rsquor;[’] rsquo;[’] rscr;[𝓇] rsqb;[]] rsh;[↱]. - "\x05aquo;\x03›\x05quor;\x03’\x04quo;\x03’\x03cr;\x04𝓇\x03qb;\x01]\x02h;\x03↱", - // rtriltri;[⧎] rthree;[⋌] rtimes;[⋊] rtrie;[⊵] rtrif;[▸] rtri;[▹]. - "\x07riltri;\x03⧎\x05hree;\x03⋌\x05imes;\x03⋊\x04rie;\x03⊵\x04rif;\x03▸\x03ri;\x03▹", - // ruluhar;[⥨]. - "\x06luhar;\x03⥨", - // rx;[℞]. - "\x01;\x03℞", - // sacute;[ś]. - "\x05cute;\x02ś", - // sbquo;[‚]. - "\x04quo;\x03‚", - // scpolint;[⨓] scaron;[š] scedil;[ş] scnsim;[⋩] sccue;[≽] scirc;[ŝ] scnap;[⪺] scsim;[≿] scap;[⪸] scnE;[⪶] scE;[⪴] sce;[⪰] scy;[с] sc;[≻]. - "\x07polint;\x03⨓\x05aron;\x02š\x05edil;\x02ş\x05nsim;\x03⋩\x04cue;\x03≽\x04irc;\x02ŝ\x04nap;\x03⪺\x04sim;\x03≿\x03ap;\x03⪸\x03nE;\x03⪶\x02E;\x03⪴\x02e;\x03⪰\x02y;\x02с\x01;\x03≻", - // sdotb;[⊡] sdote;[⩦] sdot;[⋅]. - "\x04otb;\x03⊡\x04ote;\x03⩦\x03ot;\x03⋅", - // setminus;[∖] searrow;[↘] searhk;[⤥] seswar;[⤩] seArr;[⇘] searr;[↘] setmn;[∖] sect;[§] semi;[;] sext;[✶] sect[§]. - "\x07tminus;\x03∖\x06arrow;\x03↘\x05arhk;\x03⤥\x05swar;\x03⤩\x04Arr;\x03⇘\x04arr;\x03↘\x04tmn;\x03∖\x03ct;\x02§\x03mi;\x01;\x03xt;\x03✶\x02ct\x02§", - // sfrown;[⌢] sfr;[𝔰]. - "\x05rown;\x03⌢\x02r;\x04𝔰", - // shortparallel;[∥] shortmid;[∣] shchcy;[щ] sharp;[♯] shcy;[ш] shy;[­] shy[­]. - "\x0cortparallel;\x03∥\x07ortmid;\x03∣\x05chcy;\x02щ\x04arp;\x03♯\x03cy;\x02ш\x02y;\x02­\x01y\x02­", - // simplus;[⨤] simrarr;[⥲] sigmaf;[ς] sigmav;[ς] simdot;[⩪] sigma;[σ] simeq;[≃] simgE;[⪠] simlE;[⪟] simne;[≆] sime;[≃] simg;[⪞] siml;[⪝] sim;[∼]. - "\x06mplus;\x03⨤\x06mrarr;\x03⥲\x05gmaf;\x02ς\x05gmav;\x02ς\x05mdot;\x03⩪\x04gma;\x02σ\x04meq;\x03≃\x04mgE;\x03⪠\x04mlE;\x03⪟\x04mne;\x03≆\x03me;\x03≃\x03mg;\x03⪞\x03ml;\x03⪝\x02m;\x03∼", - // slarr;[←]. - "\x04arr;\x03←", - // smallsetminus;[∖] smeparsl;[⧤] smashp;[⨳] smile;[⌣] smtes;[⪬︀] smid;[∣] smte;[⪬] smt;[⪪]. - "\x0callsetminus;\x03∖\x07eparsl;\x03⧤\x05ashp;\x03⨳\x04ile;\x03⌣\x04tes;\x06⪬︀\x03id;\x03∣\x03te;\x03⪬\x02t;\x03⪪", - // softcy;[ь] solbar;[⌿] solb;[⧄] sopf;[𝕤] sol;[/]. - "\x05ftcy;\x02ь\x05lbar;\x03⌿\x03lb;\x03⧄\x03pf;\x04𝕤\x02l;\x01/", - // spadesuit;[♠] spades;[♠] spar;[∥]. - "\x08adesuit;\x03♠\x05ades;\x03♠\x03ar;\x03∥", - // sqsubseteq;[⊑] sqsupseteq;[⊒] sqsubset;[⊏] sqsupset;[⊐] sqcaps;[⊓︀] sqcups;[⊔︀] sqsube;[⊑] sqsupe;[⊒] square;[□] squarf;[▪] sqcap;[⊓] sqcup;[⊔] sqsub;[⊏] sqsup;[⊐] squf;[▪] squ;[□]. - "\x09subseteq;\x03⊑\x09supseteq;\x03⊒\x07subset;\x03⊏\x07supset;\x03⊐\x05caps;\x06⊓︀\x05cups;\x06⊔︀\x05sube;\x03⊑\x05supe;\x03⊒\x05uare;\x03□\x05uarf;\x03▪\x04cap;\x03⊓\x04cup;\x03⊔\x04sub;\x03⊏\x04sup;\x03⊐\x03uf;\x03▪\x02u;\x03□", - // srarr;[→]. - "\x04arr;\x03→", - // ssetmn;[∖] ssmile;[⌣] sstarf;[⋆] sscr;[𝓈]. - "\x05etmn;\x03∖\x05mile;\x03⌣\x05tarf;\x03⋆\x03cr;\x04𝓈", - // straightepsilon;[ϵ] straightphi;[ϕ] starf;[★] strns;[¯] star;[☆]. - "\x0eraightepsilon;\x02ϵ\x0araightphi;\x02ϕ\x04arf;\x03★\x04rns;\x02¯\x03ar;\x03☆", - // succcurlyeq;[≽] succnapprox;[⪺] subsetneqq;[⫋] succapprox;[⪸] supsetneqq;[⫌] subseteqq;[⫅] subsetneq;[⊊] supseteqq;[⫆] supsetneq;[⊋] subseteq;[⊆] succneqq;[⪶] succnsim;[⋩] supseteq;[⊇] subedot;[⫃] submult;[⫁] subplus;[⪿] subrarr;[⥹] succsim;[≿] supdsub;[⫘] supedot;[⫄] suphsol;[⟉] suphsub;[⫗] suplarr;[⥻] supmult;[⫂] supplus;[⫀] subdot;[⪽] subset;[⊂] subsim;[⫇] subsub;[⫕] subsup;[⫓] succeq;[⪰] supdot;[⪾] supset;[⊃] supsim;[⫈] supsub;[⫔] supsup;[⫖] subnE;[⫋] subne;[⊊] supnE;[⫌] supne;[⊋] subE;[⫅] sube;[⊆] succ;[≻] sung;[♪] sup1;[¹] sup2;[²] sup3;[³] supE;[⫆] supe;[⊇] sub;[⊂] sum;[∑] sup1[¹] sup2[²] sup3[³] sup;[⊃]. - "\x0acccurlyeq;\x03≽\x0accnapprox;\x03⪺\x09bsetneqq;\x03⫋\x09ccapprox;\x03⪸\x09psetneqq;\x03⫌\x08bseteqq;\x03⫅\x08bsetneq;\x03⊊\x08pseteqq;\x03⫆\x08psetneq;\x03⊋\x07bseteq;\x03⊆\x07ccneqq;\x03⪶\x07ccnsim;\x03⋩\x07pseteq;\x03⊇\x06bedot;\x03⫃\x06bmult;\x03⫁\x06bplus;\x03⪿\x06brarr;\x03⥹\x06ccsim;\x03≿\x06pdsub;\x03⫘\x06pedot;\x03⫄\x06phsol;\x03⟉\x06phsub;\x03⫗\x06plarr;\x03⥻\x06pmult;\x03⫂\x06pplus;\x03⫀\x05bdot;\x03⪽\x05bset;\x03⊂\x05bsim;\x03⫇\x05bsub;\x03⫕\x05bsup;\x03⫓\x05cceq;\x03⪰\x05pdot;\x03⪾\x05pset;\x03⊃\x05psim;\x03⫈\x05psub;\x03⫔\x05psup;\x03⫖\x04bnE;\x03⫋\x04bne;\x03⊊\x04pnE;\x03⫌\x04pne;\x03⊋\x03bE;\x03⫅\x03be;\x03⊆\x03cc;\x03≻\x03ng;\x03♪\x03p1;\x02¹\x03p2;\x02²\x03p3;\x02³\x03pE;\x03⫆\x03pe;\x03⊇\x02b;\x03⊂\x02m;\x03∑\x02p1\x02¹\x02p2\x02²\x02p3\x02³\x02p;\x03⊃", - // swarrow;[↙] swarhk;[⤦] swnwar;[⤪] swArr;[⇙] swarr;[↙]. - "\x06arrow;\x03↙\x05arhk;\x03⤦\x05nwar;\x03⤪\x04Arr;\x03⇙\x04arr;\x03↙", - // szlig;[ß] szlig[ß]. - "\x04lig;\x02ß\x03lig\x02ß", - // target;[⌖] tau;[τ]. - "\x05rget;\x03⌖\x02u;\x02τ", - // tbrk;[⎴]. - "\x03rk;\x03⎴", - // tcaron;[ť] tcedil;[ţ] tcy;[т]. - "\x05aron;\x02ť\x05edil;\x02ţ\x02y;\x02т", - // tdot;[⃛]. - "\x03ot;\x03⃛", - // telrec;[⌕]. - "\x05lrec;\x03⌕", - // tfr;[𝔱]. - "\x02r;\x04𝔱", - // thickapprox;[≈] therefore;[∴] thetasym;[ϑ] thicksim;[∼] there4;[∴] thetav;[ϑ] thinsp;[ ] thksim;[∼] theta;[θ] thkap;[≈] thorn;[þ] thorn[þ]. - "\x0aickapprox;\x03≈\x08erefore;\x03∴\x07etasym;\x02ϑ\x07icksim;\x03∼\x05ere4;\x03∴\x05etav;\x02ϑ\x05insp;\x03 \x05ksim;\x03∼\x04eta;\x02θ\x04kap;\x03≈\x04orn;\x02þ\x03orn\x02þ", - // timesbar;[⨱] timesb;[⊠] timesd;[⨰] tilde;[˜] times;[×] times[×] tint;[∭]. - "\x07mesbar;\x03⨱\x05mesb;\x03⊠\x05mesd;\x03⨰\x04lde;\x02˜\x04mes;\x02×\x03mes\x02×\x03nt;\x03∭", - // topfork;[⫚] topbot;[⌶] topcir;[⫱] toea;[⤨] topf;[𝕥] tosa;[⤩] top;[⊤]. - "\x06pfork;\x03⫚\x05pbot;\x03⌶\x05pcir;\x03⫱\x03ea;\x03⤨\x03pf;\x04𝕥\x03sa;\x03⤩\x02p;\x03⊤", - // tprime;[‴]. - "\x05rime;\x03‴", - // trianglerighteq;[⊵] trianglelefteq;[⊴] triangleright;[▹] triangledown;[▿] triangleleft;[◃] triangleq;[≜] triangle;[▵] triminus;[⨺] trpezium;[⏢] triplus;[⨹] tritime;[⨻] tridot;[◬] trade;[™] trisb;[⧍] trie;[≜]. - "\x0eianglerighteq;\x03⊵\x0dianglelefteq;\x03⊴\x0ciangleright;\x03▹\x0biangledown;\x03▿\x0biangleleft;\x03◃\x08iangleq;\x03≜\x07iangle;\x03▵\x07iminus;\x03⨺\x07pezium;\x03⏢\x06iplus;\x03⨹\x06itime;\x03⨻\x05idot;\x03◬\x04ade;\x03™\x04isb;\x03⧍\x03ie;\x03≜", - // tstrok;[ŧ] tshcy;[ћ] tscr;[𝓉] tscy;[ц]. - "\x05trok;\x02ŧ\x04hcy;\x02ћ\x03cr;\x04𝓉\x03cy;\x02ц", - // twoheadrightarrow;[↠] twoheadleftarrow;[↞] twixt;[≬]. - "\x10oheadrightarrow;\x03↠\x0foheadleftarrow;\x03↞\x04ixt;\x03≬", - // uArr;[⇑]. - "\x03rr;\x03⇑", - // uHar;[⥣]. - "\x03ar;\x03⥣", - // uacute;[ú] uacute[ú] uarr;[↑]. - "\x05cute;\x02ú\x04cute\x02ú\x03rr;\x03↑", - // ubreve;[ŭ] ubrcy;[ў]. - "\x05reve;\x02ŭ\x04rcy;\x02ў", - // ucirc;[û] ucirc[û] ucy;[у]. - "\x04irc;\x02û\x03irc\x02û\x02y;\x02у", - // udblac;[ű] udarr;[⇅] udhar;[⥮]. - "\x05blac;\x02ű\x04arr;\x03⇅\x04har;\x03⥮", - // ufisht;[⥾] ufr;[𝔲]. - "\x05isht;\x03⥾\x02r;\x04𝔲", - // ugrave;[ù] ugrave[ù]. - "\x05rave;\x02ù\x04rave\x02ù", - // uharl;[↿] uharr;[↾] uhblk;[▀]. - "\x04arl;\x03↿\x04arr;\x03↾\x04blk;\x03▀", - // ulcorner;[⌜] ulcorn;[⌜] ulcrop;[⌏] ultri;[◸]. - "\x07corner;\x03⌜\x05corn;\x03⌜\x05crop;\x03⌏\x04tri;\x03◸", - // umacr;[ū] uml;[¨] uml[¨]. - "\x04acr;\x02ū\x02l;\x02¨\x01l\x02¨", - // uogon;[ų] uopf;[𝕦]. - "\x04gon;\x02ų\x03pf;\x04𝕦", - // upharpoonright;[↾] upharpoonleft;[↿] updownarrow;[↕] upuparrows;[⇈] uparrow;[↑] upsilon;[υ] uplus;[⊎] upsih;[ϒ] upsi;[υ]. - "\x0dharpoonright;\x03↾\x0charpoonleft;\x03↿\x0adownarrow;\x03↕\x09uparrows;\x03⇈\x06arrow;\x03↑\x06silon;\x02υ\x04lus;\x03⊎\x04sih;\x02ϒ\x03si;\x02υ", - // urcorner;[⌝] urcorn;[⌝] urcrop;[⌎] uring;[ů] urtri;[◹]. - "\x07corner;\x03⌝\x05corn;\x03⌝\x05crop;\x03⌎\x04ing;\x02ů\x04tri;\x03◹", - // uscr;[𝓊]. - "\x03cr;\x04𝓊", - // utilde;[ũ] utdot;[⋰] utrif;[▴] utri;[▵]. - "\x05ilde;\x02ũ\x04dot;\x03⋰\x04rif;\x03▴\x03ri;\x03▵", - // uuarr;[⇈] uuml;[ü] uuml[ü]. - "\x04arr;\x03⇈\x03ml;\x02ü\x02ml\x02ü", - // uwangle;[⦧]. - "\x06angle;\x03⦧", - // vArr;[⇕]. - "\x03rr;\x03⇕", - // vBarv;[⫩] vBar;[⫨]. - "\x04arv;\x03⫩\x03ar;\x03⫨", - // vDash;[⊨]. - "\x04ash;\x03⊨", - // vartriangleright;[⊳] vartriangleleft;[⊲] varsubsetneqq;[⫋︀] varsupsetneqq;[⫌︀] varsubsetneq;[⊊︀] varsupsetneq;[⊋︀] varepsilon;[ϵ] varnothing;[∅] varpropto;[∝] varkappa;[ϰ] varsigma;[ς] vartheta;[ϑ] vangrt;[⦜] varphi;[ϕ] varrho;[ϱ] varpi;[ϖ] varr;[↕]. - "\x0frtriangleright;\x03⊳\x0ertriangleleft;\x03⊲\x0crsubsetneqq;\x06⫋︀\x0crsupsetneqq;\x06⫌︀\x0brsubsetneq;\x06⊊︀\x0brsupsetneq;\x06⊋︀\x09repsilon;\x02ϵ\x09rnothing;\x03∅\x08rpropto;\x03∝\x07rkappa;\x02ϰ\x07rsigma;\x02ς\x07rtheta;\x02ϑ\x05ngrt;\x03⦜\x05rphi;\x02ϕ\x05rrho;\x02ϱ\x04rpi;\x02ϖ\x03rr;\x03↕", - // vcy;[в]. - "\x02y;\x02в", - // vdash;[⊢]. - "\x04ash;\x03⊢", - // veebar;[⊻] vellip;[⋮] verbar;[|] veeeq;[≚] vert;[|] vee;[∨]. - "\x05ebar;\x03⊻\x05llip;\x03⋮\x05rbar;\x01|\x04eeq;\x03≚\x03rt;\x01|\x02e;\x03∨", - // vfr;[𝔳]. - "\x02r;\x04𝔳", - // vltri;[⊲]. - "\x04tri;\x03⊲", - // vnsub;[⊂⃒] vnsup;[⊃⃒]. - "\x04sub;\x06⊂⃒\x04sup;\x06⊃⃒", - // vopf;[𝕧]. - "\x03pf;\x04𝕧", - // vprop;[∝]. - "\x04rop;\x03∝", - // vrtri;[⊳]. - "\x04tri;\x03⊳", - // vsubnE;[⫋︀] vsubne;[⊊︀] vsupnE;[⫌︀] vsupne;[⊋︀] vscr;[𝓋]. - "\x05ubnE;\x06⫋︀\x05ubne;\x06⊊︀\x05upnE;\x06⫌︀\x05upne;\x06⊋︀\x03cr;\x04𝓋", - // vzigzag;[⦚]. - "\x06igzag;\x03⦚", - // wcirc;[ŵ]. - "\x04irc;\x02ŵ", - // wedbar;[⩟] wedgeq;[≙] weierp;[℘] wedge;[∧]. - "\x05dbar;\x03⩟\x05dgeq;\x03≙\x05ierp;\x03℘\x04dge;\x03∧", - // wfr;[𝔴]. - "\x02r;\x04𝔴", - // wopf;[𝕨]. - "\x03pf;\x04𝕨", - // wp;[℘]. - "\x01;\x03℘", - // wreath;[≀] wr;[≀]. - "\x05eath;\x03≀\x01;\x03≀", - // wscr;[𝓌]. - "\x03cr;\x04𝓌", - // xcirc;[◯] xcap;[⋂] xcup;[⋃]. - "\x04irc;\x03◯\x03ap;\x03⋂\x03up;\x03⋃", - // xdtri;[▽]. - "\x04tri;\x03▽", - // xfr;[𝔵]. - "\x02r;\x04𝔵", - // xhArr;[⟺] xharr;[⟷]. - "\x04Arr;\x03⟺\x04arr;\x03⟷", - // xi;[ξ]. - "\x01;\x02ξ", - // xlArr;[⟸] xlarr;[⟵]. - "\x04Arr;\x03⟸\x04arr;\x03⟵", - // xmap;[⟼]. - "\x03ap;\x03⟼", - // xnis;[⋻]. - "\x03is;\x03⋻", - // xoplus;[⨁] xotime;[⨂] xodot;[⨀] xopf;[𝕩]. - "\x05plus;\x03⨁\x05time;\x03⨂\x04dot;\x03⨀\x03pf;\x04𝕩", - // xrArr;[⟹] xrarr;[⟶]. - "\x04Arr;\x03⟹\x04arr;\x03⟶", - // xsqcup;[⨆] xscr;[𝓍]. - "\x05qcup;\x03⨆\x03cr;\x04𝓍", - // xuplus;[⨄] xutri;[△]. - "\x05plus;\x03⨄\x04tri;\x03△", - // xvee;[⋁]. - "\x03ee;\x03⋁", - // xwedge;[⋀]. - "\x05edge;\x03⋀", - // yacute;[ý] yacute[ý] yacy;[я]. - "\x05cute;\x02ý\x04cute\x02ý\x03cy;\x02я", - // ycirc;[ŷ] ycy;[ы]. - "\x04irc;\x02ŷ\x02y;\x02ы", - // yen;[¥] yen[¥]. - "\x02n;\x02¥\x01n\x02¥", - // yfr;[𝔶]. - "\x02r;\x04𝔶", - // yicy;[ї]. - "\x03cy;\x02ї", - // yopf;[𝕪]. - "\x03pf;\x04𝕪", - // yscr;[𝓎]. - "\x03cr;\x04𝓎", - // yucy;[ю] yuml;[ÿ] yuml[ÿ]. - "\x03cy;\x02ю\x03ml;\x02ÿ\x02ml\x02ÿ", - // zacute;[ź]. - "\x05cute;\x02ź", - // zcaron;[ž] zcy;[з]. - "\x05aron;\x02ž\x02y;\x02з", - // zdot;[ż]. - "\x03ot;\x02ż", - // zeetrf;[ℨ] zeta;[ζ]. - "\x05etrf;\x03ℨ\x03ta;\x02ζ", - // zfr;[𝔷]. - "\x02r;\x04𝔷", - // zhcy;[ж]. - "\x03cy;\x02ж", - // zigrarr;[⇝]. - "\x06grarr;\x03⇝", - // zopf;[𝕫]. - "\x03pf;\x04𝕫", - // zscr;[𝓏]. - "\x03cr;\x04𝓏", - // zwnj;[‌] zwj;[‍]. - "\x03nj;\x03‌\x02j;\x03‍", - ), - "small_words" => "GT\x00LT\x00gt\x00lt\x00", - "small_mappings" => array( - ">", - "<", - ">", - "<", - ) - ) - ); -} diff --git a/lib/compat/wordpress-6.6/option.php b/lib/compat/wordpress-6.6/option.php deleted file mode 100644 index 9ec81467c261da..00000000000000 --- a/lib/compat/wordpress-6.6/option.php +++ /dev/null @@ -1,53 +0,0 @@ - __( 'Title' ), - 'blogdescription' => __( 'Tagline' ), - 'show_on_front' => __( 'Show on front' ), - 'page_on_front' => __( 'Page on front' ), - 'posts_per_page' => __( 'Maximum posts per page' ), - 'default_comment_status' => __( 'Allow comments on new posts' ), - ); - - if ( isset( $settings_label_map[ $option_name ] ) ) { - $args['label'] = $settings_label_map[ $option_name ]; - } - - // Don't update schema when a setting isn't exposed via REST API. - if ( ! isset( $args['show_in_rest'] ) ) { - return $args; - } - - // Don't update schema when label isn't provided. - if ( ! isset( $args['label'] ) ) { - return $args; - } - - $schema = array( 'title' => $args['label'] ); - if ( ! is_array( $args['show_in_rest'] ) ) { - $args['show_in_rest'] = array( - 'schema' => $schema, - ); - return $args; - } - - if ( ! empty( $args['show_in_rest']['schema'] ) ) { - $args['show_in_rest']['schema'] = array_merge( $args['show_in_rest']['schema'], $schema ); - } else { - $args['show_in_rest']['schema'] = $schema; - } - - return $args; -} -add_filter( 'register_setting_args', 'gutenberg_update_initial_settings', 10, 4 ); diff --git a/lib/compat/wordpress-6.6/post.php b/lib/compat/wordpress-6.6/post.php deleted file mode 100644 index 8415f3bf42f18f..00000000000000 --- a/lib/compat/wordpress-6.6/post.php +++ /dev/null @@ -1,39 +0,0 @@ -item_updated = __( 'Template updated.', 'gutenberg' ); - return $labels; -} -add_filter( 'post_type_labels_wp_template', 'gutenberg_update_wp_template_labels', 10, 1 ); - -/** - * Updates the labels for the template parts post type. - * - * @param object $labels Object with labels for the post type as member variables. - * @return object Object with all the labels as member variables. - */ -function gutenberg_update_wp_template__part_labels( $labels ) { - $labels->item_updated = __( 'Template part updated.', 'gutenberg' ); - return $labels; -} -add_filter( 'post_type_labels_wp_template_part', 'gutenberg_update_wp_template__part_labels', 10, 1 ); diff --git a/lib/compat/wordpress-6.6/resolve-patterns.php b/lib/compat/wordpress-6.6/resolve-patterns.php deleted file mode 100644 index 5105619c42613c..00000000000000 --- a/lib/compat/wordpress-6.6/resolve-patterns.php +++ /dev/null @@ -1,84 +0,0 @@ -get_registered( $slug ); - - // Skip unknown patterns. - if ( ! $pattern ) { - ++$i; - continue; - } - - $blocks_to_insert = parse_blocks( $pattern['content'] ); - $seen_refs[ $slug ] = true; - $blocks_to_insert = gutenberg_replace_pattern_blocks( $blocks_to_insert ); - unset( $seen_refs[ $slug ] ); - array_splice( $blocks, $i, 1, $blocks_to_insert ); - - // If we have inner content, we need to insert nulls in the - // inner content array, otherwise serialize_blocks will skip - // blocks. - if ( $inner_content ) { - $null_indices = array_keys( $inner_content, null, true ); - $content_index = $null_indices[ $i ]; - $nulls = array_fill( 0, count( $blocks_to_insert ), null ); - array_splice( $inner_content, $content_index, 1, $nulls ); - } - - // Skip inserted blocks. - $i += count( $blocks_to_insert ); - } else { - if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) { - $blocks[ $i ]['innerBlocks'] = gutenberg_replace_pattern_blocks( - $blocks[ $i ]['innerBlocks'], - $blocks[ $i ]['innerContent'] - ); - } - ++$i; - } - } - return $blocks; -} - -function gutenberg_replace_pattern_blocks_patterns_endpoint( $result, $server, $request ) { - if ( $request->get_route() !== '/wp/v2/block-patterns/patterns' ) { - return $result; - } - - $data = $result->get_data(); - - foreach ( $data as $index => $pattern ) { - $blocks = parse_blocks( $pattern['content'] ); - $blocks = gutenberg_replace_pattern_blocks( $blocks ); - $data[ $index ]['content'] = serialize_blocks( $blocks ); - } - - $result->set_data( $data ); - - return $result; -} - -// Similarly, for patterns, we can avoid the double parse here: -// https://github.com/WordPress/wordpress-develop/blob/02fb53498f1ce7e63d807b9bafc47a7dba19d169/src/wp-includes/class-wp-block-patterns-registry.php#L175 -add_filter( 'rest_post_dispatch', 'gutenberg_replace_pattern_blocks_patterns_endpoint', 10, 3 ); diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php deleted file mode 100644 index eadd3b1d376a72..00000000000000 --- a/lib/compat/wordpress-6.6/rest-api.php +++ /dev/null @@ -1,216 +0,0 @@ - true, - 'show_in_rest' => true, - ), - 'names' - ); - - if ( ! empty( $post_types ) ) { - register_rest_field( - $post_types, - 'class_list', - array( - 'get_callback' => 'gutenberg_add_class_list_to_api_response', - 'schema' => array( - 'description' => __( 'An array of the class names for the post container element.', 'gutenberg' ), - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - ), - ) - ); - } -} -add_action( 'rest_api_init', 'gutenberg_add_class_list_to_public_post_types' ); - - -/** - * Registers the Global Styles Revisions REST API routes. - */ -function gutenberg_register_global_styles_revisions_endpoints() { - $global_styles_revisions_controller = new Gutenberg_REST_Global_Styles_Revisions_Controller_6_6(); - $global_styles_revisions_controller->register_routes(); -} - -add_action( 'rest_api_init', 'gutenberg_register_global_styles_revisions_endpoints' ); - -/** - * Adds `stylesheet_uri` fields to WP_REST_Themes_Controller class. - */ -function gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field() { - register_rest_field( - 'theme', - 'stylesheet_uri', - array( - 'get_callback' => function ( $item ) { - if ( ! empty( $item['stylesheet'] ) ) { - $theme = wp_get_theme( $item['stylesheet'] ); - $current_theme = wp_get_theme(); - if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { - return get_stylesheet_directory_uri(); - } else { - return $theme->get_stylesheet_directory_uri(); - } - } - - return null; - }, - 'schema' => array( - 'type' => 'string', - 'description' => __( 'The uri for the theme\'s stylesheet directory.', 'gutenberg' ), - 'format' => 'uri', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_stylesheet_directory_uri_field' ); - -/** - * Adds `template_uri` fields to WP_REST_Themes_Controller class. - */ -function gutenberg_register_wp_rest_themes_template_directory_uri_field() { - register_rest_field( - 'theme', - 'template_uri', - array( - 'get_callback' => function ( $item ) { - if ( ! empty( $item['stylesheet'] ) ) { - $theme = wp_get_theme( $item['stylesheet'] ); - $current_theme = wp_get_theme(); - if ( $theme->get_stylesheet() === $current_theme->get_stylesheet() ) { - return get_template_directory_uri(); - } else { - return $theme->get_template_directory_uri(); - } - } - - return null; - }, - 'schema' => array( - 'type' => 'string', - 'description' => __( 'The uri for the theme\'s template directory. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet directory.', 'gutenberg' ), - 'format' => 'uri', - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_wp_rest_themes_template_directory_uri_field' ); - -/** - * Adds `template` and `template_lock` fields to WP_REST_Post_Types_Controller class. - */ -function gutenberg_register_wp_rest_post_types_controller_fields() { - register_rest_field( - 'type', - 'template', - array( - 'get_callback' => function ( $item ) { - $post_type = get_post_type_object( $item['slug'] ); - if ( ! empty( $post_type ) ) { - return $post_type->template ?? array(); - } - }, - 'schema' => array( - 'type' => 'array', - 'description' => __( 'The block template associated with the post type.', 'gutenberg' ), - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); - register_rest_field( - 'type', - 'template_lock', - array( - 'get_callback' => function ( $item ) { - $post_type = get_post_type_object( $item['slug'] ); - if ( ! empty( $post_type ) ) { - return ! empty( $post_type->template_lock ) ? $post_type->template_lock : false; - } - }, - 'schema' => array( - 'type' => array( 'string', 'boolean' ), - 'enum' => array( 'all', 'insert', 'contentOnly', false ), - 'description' => __( 'The template_lock associated with the post type, or false if none.', 'gutenberg' ), - 'readonly' => true, - 'context' => array( 'view', 'edit', 'embed' ), - ), - ) - ); -} -add_action( 'rest_api_init', 'gutenberg_register_wp_rest_post_types_controller_fields' ); - -/** - * Preload theme and global styles paths to avoid flash of variation styles in post editor. - * - * @param array $paths REST API paths to preload. - * @param WP_Block_Editor_Context $context Current block editor context. - * @return array Filtered preload paths. - */ -function gutenberg_block_editor_preload_paths_6_6( $paths, $context ) { - if ( 'core/edit-post' === $context->name ) { - $paths[] = '/wp/v2/global-styles/themes/' . get_stylesheet(); - $paths[] = '/wp/v2/themes?context=edit&status=active'; - $paths[] = '/wp/v2/global-styles/' . WP_Theme_JSON_Resolver_Gutenberg::get_user_global_styles_post_id() . '?context=edit'; - } - return $paths; -} -add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_block_editor_preload_paths_6_6', 10, 2 ); diff --git a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php index ed67dded75ecb1..e5f6eb126f2a6a 100644 --- a/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php +++ b/lib/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php @@ -9,7 +9,7 @@ * Gutenberg_REST_Templates_Controller_6_7 class * */ -class Gutenberg_REST_Templates_Controller_6_7 extends Gutenberg_REST_Templates_Controller_6_6 { +class Gutenberg_REST_Templates_Controller_6_7 extends WP_REST_Templates_Controller { /** * Returns the given template * diff --git a/lib/compat/wordpress-6.7/compat.php b/lib/compat/wordpress-6.7/compat.php index e58eca56ef71f9..33c123a14860d2 100644 --- a/lib/compat/wordpress-6.7/compat.php +++ b/lib/compat/wordpress-6.7/compat.php @@ -46,7 +46,7 @@ function _gutenberg_add_block_templates_from_registry( $query_result, $query, $t // See: https://github.com/WordPress/gutenberg/issues/65584 $template_files_query = $query; unset( $template_files_query['post_type'] ); - $template_files = _gutenberg_get_block_templates_files( $template_type, $template_files_query ); + $template_files = _get_block_templates_files( $template_type, $template_files_query ); /* * Add templates registered in the template registry. Filtering out the ones which have a theme file. diff --git a/lib/load.php b/lib/load.php index 100160176f3911..85d1c7e3292b50 100644 --- a/lib/load.php +++ b/lib/load.php @@ -35,11 +35,6 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/experimental/class-wp-rest-block-editor-settings-controller.php'; } - // WordPress 6.6 compat. - require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-global-styles-revisions-controller-6-6.php'; - require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-rest-templates-controller-6-6.php'; - require __DIR__ . '/compat/wordpress-6.6/rest-api.php'; - // WordPress 6.7 compat. require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-posts-controller-6-7.php'; require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-rest-templates-controller-6-7.php'; @@ -72,18 +67,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/plugin/edit-site-routes-backwards-compat.php'; require __DIR__ . '/compat/plugin/fonts.php'; -// The Token Map was created during 6.6 in order to support the HTML API. It must be loaded before it. -require __DIR__ . '/compat/wordpress-6.6/class-gutenberg-token-map-6-6.php'; +// The Token Map was created to support the HTML API. It must be loaded before it. require __DIR__ . '/compat/wordpress-6.7/class-gutenberg-token-map-6-7.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/gutenberg-html5-named-character-references-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-decoder-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-tag-processor-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-open-elements-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-stack-event-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-state-6-6.php'; -require __DIR__ . '/compat/wordpress-6.6/html-api/class-gutenberg-html-processor-6-6.php'; - // Type annotations were added in 6.7 so every file is updated. require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-active-formatting-elements-6-7.php'; require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-attribute-token-6-7.php'; @@ -98,17 +84,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-state-6-7.php'; require __DIR__ . '/compat/wordpress-6.7/html-api/class-gutenberg-html-processor-6-7.php'; -// WordPress 6.6 compat. -require __DIR__ . '/compat/wordpress-6.6/admin-bar.php'; -require __DIR__ . '/compat/wordpress-6.6/blocks.php'; -require __DIR__ . '/compat/wordpress-6.6/block-editor.php'; -require __DIR__ . '/compat/wordpress-6.6/compat.php'; -require __DIR__ . '/compat/wordpress-6.6/resolve-patterns.php'; -require __DIR__ . '/compat/wordpress-6.6/block-bindings/pattern-overrides.php'; -require __DIR__ . '/compat/wordpress-6.6/block-template-utils.php'; -require __DIR__ . '/compat/wordpress-6.6/option.php'; -require __DIR__ . '/compat/wordpress-6.6/post.php'; - // WordPress 6.7 compat. require __DIR__ . '/compat/wordpress-6.7/block-templates.php'; require __DIR__ . '/compat/wordpress-6.7/blocks.php'; diff --git a/lib/rest-api.php b/lib/rest-api.php index 7570bb19737233..424927acf1f4a0 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -19,11 +19,10 @@ * @return array Array of arguments for registering a post type. */ function gutenberg_override_global_styles_endpoint( array $args ): array { - $args['rest_controller_class'] = 'WP_REST_Global_Styles_Controller_Gutenberg'; - $args['revisions_rest_controller_class'] = 'Gutenberg_REST_Global_Styles_Revisions_Controller_6_6'; - $args['late_route_registration'] = true; - $args['show_in_rest'] = true; - $args['rest_base'] = 'global-styles'; + $args['rest_controller_class'] = 'WP_REST_Global_Styles_Controller_Gutenberg'; + $args['late_route_registration'] = true; + $args['show_in_rest'] = true; + $args['rest_base'] = 'global-styles'; return $args; } diff --git a/phpunit/blocks/get-block-templates-test.php b/phpunit/blocks/get-block-templates-test.php deleted file mode 100644 index 7c8045a1f25f74..00000000000000 --- a/phpunit/blocks/get-block-templates-test.php +++ /dev/null @@ -1,109 +0,0 @@ -theme_root = realpath( __DIR__ . '/../data/themedir1' ); - $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; - - // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. - $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); - - add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); - add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); - add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); - // Clear caches. - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - switch_theme( self::TEST_THEME ); - } - - public function tear_down() { - $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; - wp_clean_themes_cache(); - unset( $GLOBALS['wp_themes'] ); - parent::tear_down(); - } - - public function filter_set_theme_root() { - return $this->theme_root; - } - - /** - * Gets the template IDs from the given array. - * - * @param object[] $templates Array of template objects to parse. - * @return string[] The template IDs. - */ - private function get_template_ids( $templates ) { - return array_map( - static function ( $template ) { - return $template->id; - }, - $templates - ); - } - - /** - * @dataProvider data_get_block_templates_should_respect_posttypes_property - * @ticket 55881 - * - * @param string $post_type Post type for query. - * @param array $expected Expected template IDs. - */ - public function test_get_block_templates_should_respect_posttypes_property( $post_type, $expected ) { - $templates = gutenberg_get_block_templates( array( 'post_type' => $post_type ) ); - - $this->assertSameSets( - $expected, - $this->get_template_ids( $templates ) - ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_get_block_templates_should_respect_posttypes_property() { - // @code-merge: This code will go into Core's tests/phpunit/tests/blocks/getBlockTemplates.php. - return array( - 'post' => array( - 'post_type' => 'post', - 'expected' => array( - 'block-theme//custom-hero-template', - 'block-theme//custom-single-post-template', - ), - ), - 'page' => array( - 'post_type' => 'page', - 'expected' => array( - 'block-theme//custom-hero-template', - 'block-theme//page-home', - ), - ), - ); - } -} diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index c8ca89c7c1f6c7..e47a36f73f4188 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -3205,7 +3205,7 @@ public function test_get_editor_settings_blank() { public function test_get_editor_settings_custom_units_can_be_disabled() { add_theme_support( 'custom-units', array() ); - $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); remove_theme_support( 'custom-units' ); $expected = array( @@ -3218,7 +3218,7 @@ public function test_get_editor_settings_custom_units_can_be_disabled() { public function test_get_editor_settings_custom_units_can_be_enabled() { add_theme_support( 'custom-units' ); - $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); remove_theme_support( 'custom-units' ); $expected = array( @@ -3231,7 +3231,7 @@ public function test_get_editor_settings_custom_units_can_be_enabled() { public function test_get_editor_settings_custom_units_can_be_filtered() { add_theme_support( 'custom-units', 'rem', 'em' ); - $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_classic_theme_supports_block_editor_settings() ); + $actual = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); remove_theme_support( 'custom-units' ); $expected = array( diff --git a/readme.txt b/readme.txt index 4bd7ddc36d6e9b..066de4a678a552 100644 --- a/readme.txt +++ b/readme.txt @@ -1,6 +1,6 @@ === Gutenberg === Contributors: matveb, joen, karmatosed -Tested up to: 6.6 +Tested up to: 6.7 Stable tag: V.V.V License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html From b5528727a447619ca0ee6b9ddf3e26b29cdac092 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 20 Nov 2024 16:02:56 +0400 Subject: [PATCH 180/605] Home Link: Remove label attribute synchronization (#67151) Co-authored-by: Mamaduka Co-authored-by: t-hamano --- packages/block-library/src/home-link/edit.js | 72 +++++++------------ .../block-library/src/home-link/index.php | 3 - 2 files changed, 27 insertions(+), 48 deletions(-) diff --git a/packages/block-library/src/home-link/edit.js b/packages/block-library/src/home-link/edit.js index fa6b27a3e2128b..793e13515411db 100644 --- a/packages/block-library/src/home-link/edit.js +++ b/packages/block-library/src/home-link/edit.js @@ -6,15 +6,10 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { - RichText, - useBlockProps, - store as blockEditorStore, -} from '@wordpress/block-editor'; +import { RichText, useBlockProps } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { useEffect } from '@wordpress/element'; const preventDefault = ( event ) => event.preventDefault(); @@ -24,8 +19,6 @@ export default function HomeEdit( { attributes, setAttributes, context } ) { return select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) ?.home; }, [] ); - const { __unstableMarkNextChangeAsNotPersistent } = - useDispatch( blockEditorStore ); const { textColor, backgroundColor, style } = context; const blockProps = useBlockProps( { @@ -41,42 +34,31 @@ export default function HomeEdit( { attributes, setAttributes, context } ) { }, } ); - const { label } = attributes; - - useEffect( () => { - if ( label === undefined ) { - __unstableMarkNextChangeAsNotPersistent(); - setAttributes( { label: __( 'Home' ) } ); - } - }, [ label ] ); - return ( - <> - - + ); } diff --git a/packages/block-library/src/home-link/index.php b/packages/block-library/src/home-link/index.php index fb7235834459d0..d61aa0bc235e22 100644 --- a/packages/block-library/src/home-link/index.php +++ b/packages/block-library/src/home-link/index.php @@ -137,9 +137,6 @@ function block_core_home_link_build_li_wrapper_attributes( $context ) { */ function render_block_core_home_link( $attributes, $content, $block ) { if ( empty( $attributes['label'] ) ) { - // Using a fallback for the label attribute allows rendering the block even if no attributes have been set, - // e.g. when using the block as a hooked block. - // Note that the fallback value needs to be kept in sync with the one set in `edit.js` (upon first loading the block in the editor). $attributes['label'] = __( 'Home' ); } $aria_current = ''; From 2eeb1278411dab7030646f7e9d36293b86a2e015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:52:44 +0100 Subject: [PATCH 181/605] DataViews: reorganize docs for actions (#67159) --- packages/dataviews/README.md | 223 ++++++++++++++++++++++++++++++++--- 1 file changed, 206 insertions(+), 17 deletions(-) diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 621e3c7ba71ce2..92426bf1e1f838 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -293,23 +293,54 @@ function MyCustomPageTable() { #### `actions`: `Object[]` -Collection of operations that can be performed upon each record. - -Each action is an object with the following properties: - -- `id`: string, required. Unique identifier of the action. For example, `move-to-trash`. -- `label`: string|function, required. User facing description of the action. For example, `Move to Trash`. It can also take a function that takes the selected items as a parameter and returns a string: this can be useful to provide a dynamic label based on the selection. -- `isPrimary`: boolean, optional. Whether the action should be listed inline (primary) or in hidden in the more actions menu (secondary). -- `icon`: SVG element. Icon to show for primary actions. It's required for a primary action, otherwise the action would be considered secondary. -- `isEligible`: function, optional. Whether the action can be performed for a given record. If not present, the action is considered to be eligible for all items. It takes the given record as input. -- `isDestructive`: boolean, optional. Whether the action can delete data, in which case the UI would communicate it via red color. -- `supportsBulk`: Whether the action can be used as a bulk action. False by default. -- `disabled`: Whether the action is disabled. False by default. -- `context`: where this action would be visible. One of `list`, `single`. -- `callback`: function, required unless `RenderModal` is provided. Callback function that takes as input the list of items to operate with, and performs the required action. -- `RenderModal`: ReactElement, optional. If an action requires that some UI be rendered in a modal, it can provide a component which takes as input the the list of `items` to operate with, `closeModal` function, and `onActionPerformed` function. When this prop is provided, the `callback` property is ignored. -- `hideModalHeader`: boolean, optional. This property is used in combination with `RenderModal` and controls the visibility of the modal's header. If the action renders a modal and doesn't hide the header, the action's label is going to be used in the modal's header. -- `modalHeader`: string, optional. The header of the modal. +A list of actions that can be performed on the dataset. See "Actions API" for more details. + +Example: + +```js +const actions = [ + { + id: 'view', + label: 'View', + isPrimary: true, + icon: , + isEligible: ( item ) => item.status === 'published' + callback: ( items ) => { + console.log( 'Viewing item:', items[0] ); + }, + }, + { + id: 'edit', + label: 'Edit', + icon: , + supportsBulk: true, + callback: ( items ) => { + console.log( 'Editing items:', items ); + } + }, + { + id: 'delete', + label: 'Delete', + isDestructive: true, + supportsBulk: true, + RenderModal: ( { items, closeModal, onActionPerformed } ) => ( +
+

Are you sure you want to delete { items.length } item(s)?

+ +
+ ) + } +]; +``` #### `paginationInfo`: `Object` @@ -502,6 +533,164 @@ Parameters: Returns a boolean indicating if the item is valid (true) or not (false). +## Actions API + +### `id` + +The unique identifier of the action. + +- Type: `string` +- Required +- Example: `move-to-trash` + +### `label` + +The user facing description of the action. + +- Type: `string | function` +- Required +- Example: + +```js +{ + label: Move to Trash +} +``` + +or + +```js +{ + label: ( items ) => items.length > 1 ? 'Delete items' : 'Delete item' +} +``` + +### `isPrimary` + +Whether the action should be displayed inline (primary) or only displayed in the "More actions" menu (secondary). + +- Type: `boolean` +- Optional + +### `icon` + +Icon to show for primary actions. + +- Type: SVG element +- Required for primary actions, optional for secondary actions. + +### `isEligible` + +Function that determines whether the action can be performed for a given record. + +- Type: `function` +- Optional. If not present, action is considered eligible for all items. +- Example: + +```js +{ + isEligible: ( item ) => item.status === 'published' +} +``` + +### `isDestructive` + +Whether the action can delete data, in which case the UI communicates it via a red color. + +- Type: `boolean` +- Optional + +### `supportsBulk` + +Whether the action can operate over multiple items at once. + +- Type: `boolean` +- Optional +- Default: `false` + +### `disabled` + +Whether the action is disabled. + +- Type: `boolean` +- Optional +- Default: `false` + +### `context` + +Where this action would be visible. + +- Type: `string` +- Optional +- One of: `list`, `single` + +### `callback` + +Function that performs the required action. + +- Type: `function` +- Either `callback` or `RenderModal` must be provided. If `RenderModal` is provided, `callback` will be ignored +- Example: + +```js +{ + callback: ( items, { onActionPerformed } ) => { + // Perform action. + onActionPerformed?.( items ); + } +} +``` + +### `RenderModal` + +Component to render UI in a modal for the action. + +- Type: `ReactElement` +- Either `callback` or `RenderModal` must be provided. If `RenderModal` is provided, `callback` will be ignored. +- Example: + +```jsx +{ + RenderModal: ( { items, closeModal, onActionPerformed } ) => { + const onSubmit = ( event ) => { + event.preventDefault(); + // Perform action. + closeModal?.(); + onActionPerformed?.( items ); + }; + return ( +
+

Modal UI

+ + + + +
+ ); + } +} +``` + +### `hideModalHeader` + +Controls visibility of the modal's header when using `RenderModal`. + +- Type: `boolean` +- Optional +- When false and using `RenderModal`, the action's label is used in modal header + +### `modalHeader` + +The header text to show in the modal. + +- Type: `string` +- Optional + + ## Fields API ### `id` From ebf1149eec2db05127bfe307974b0c409bf892ec Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:11:12 +0100 Subject: [PATCH 182/605] `FormFileUpload`: Prevent HEIC and HEIF files from being uploaded on Safari (#67139) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "Ensure HEIC files selectable from “Upload” button (#66292)" This reverts commit c5921d766c143ef6774c827c8c2f6322b2b26b6b. * Update changelog * Make it Safari conditional * Remove extra whitespaces * Update changelog * Use globalthis * Forgot a # * Make it safer Co-authored-by: cbravobernal Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: azaozz Co-authored-by: desrosj Co-authored-by: Mamaduka Co-authored-by: jsnajdr --- packages/components/CHANGELOG.md | 4 ++++ packages/components/src/form-file-upload/index.tsx | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 0638842814a7d3..b84b720e35c900 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fixes + +- `FormFileUpload`: Prevent HEIC and HEIF files from being uploaded on Safari ([#67139](https://github.com/WordPress/gutenberg/pull/67139)). + ### Deprecations - `DimensionControl`: Deprecate 36px default size ([#66705](https://github.com/WordPress/gutenberg/pull/66705)). diff --git a/packages/components/src/form-file-upload/index.tsx b/packages/components/src/form-file-upload/index.tsx index 66f0b2ea6d6480..83d563f2074764 100644 --- a/packages/components/src/form-file-upload/index.tsx +++ b/packages/components/src/form-file-upload/index.tsx @@ -50,9 +50,15 @@ export function FormFileUpload( { // @todo: Temporary fix a bug that prevents Chromium browsers from selecting ".heic" files // from the file upload. See https://core.trac.wordpress.org/ticket/62268#comment:4. // This can be removed once the Chromium fix is in the stable channel. - const compatAccept = !! accept?.includes( 'image/*' ) - ? `${ accept }, image/heic, image/heif` - : accept; + // Prevent Safari from adding "image/heic" and "image/heif" to the accept attribute. + const isSafari = + globalThis.window?.navigator.userAgent.includes( 'Safari' ) && + ! globalThis.window?.navigator.userAgent.includes( 'Chrome' ) && + ! globalThis.window?.navigator.userAgent.includes( 'Chromium' ); + const compatAccept = + ! isSafari && !! accept?.includes( 'image/*' ) + ? `${ accept }, image/heic, image/heif` + : accept; return (
From 944e6b8c04a017c50af3f8862c9120bd7c383d4e Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Wed, 20 Nov 2024 14:34:54 +0100 Subject: [PATCH 183/605] SlotFill: rewrite base Slot to functional, unify rerenderable refs (#67153) * SlotFill: rewrite base Slot to functional, unify rerenderable refs * Add changelog entry * Implement rerender with useReducer Co-authored-by: jsnajdr Co-authored-by: Mamaduka Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 1 + .../src/slot-fill/bubbles-virtually/fill.tsx | 27 +--- .../bubbles-virtually/slot-fill-provider.tsx | 2 +- packages/components/src/slot-fill/fill.ts | 2 +- .../components/src/slot-fill/provider.tsx | 25 ++-- packages/components/src/slot-fill/slot.tsx | 133 +++++++----------- packages/components/src/slot-fill/types.ts | 69 ++------- 7 files changed, 78 insertions(+), 181 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index b84b720e35c900..e5d467f22d30c8 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -33,6 +33,7 @@ ### Internal - `SlotFill`: fix dependencies of `Fill` registration effects ([#67071](https://github.com/WordPress/gutenberg/pull/67071)). +- `SlotFill`: rewrite the `Slot` component from class component to functional ([#67153](https://github.com/WordPress/gutenberg/pull/67153)). ## 28.12.0 (2024-11-16) diff --git a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx index b1b82aac5c0567..d5287adfab4178 100644 --- a/packages/components/src/slot-fill/bubbles-virtually/fill.tsx +++ b/packages/components/src/slot-fill/bubbles-virtually/fill.tsx @@ -4,8 +4,8 @@ import { useObservableValue } from '@wordpress/compose'; import { useContext, + useReducer, useRef, - useState, useEffect, createPortal, } from '@wordpress/element'; @@ -17,37 +17,20 @@ import SlotFillContext from './slot-fill-context'; import StyleProvider from '../../style-provider'; import type { FillComponentProps } from '../types'; -function useForceUpdate() { - const [ , setState ] = useState( {} ); - const mountedRef = useRef( true ); - - useEffect( () => { - mountedRef.current = true; - return () => { - mountedRef.current = false; - }; - }, [] ); - - return () => { - if ( mountedRef.current ) { - setState( {} ); - } - }; -} - export default function Fill( { name, children }: FillComponentProps ) { const registry = useContext( SlotFillContext ); const slot = useObservableValue( registry.slots, name ); - const rerender = useForceUpdate(); + const [ , rerender ] = useReducer( () => [], [] ); const ref = useRef( { rerender } ); 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. - registry.registerFill( name, ref ); + const refValue = ref.current; + registry.registerFill( name, refValue ); return () => { - registry.unregisterFill( name, ref ); + registry.unregisterFill( name, refValue ); }; }, [ registry, name ] ); 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 16a19c6569fda6..1dc5ef35ceccfe 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 @@ -70,7 +70,7 @@ function createSlotRegistry(): SlotFillBubblesVirtuallyContext { const slotFills = fills.get( name ); if ( slotFills ) { // Force update fills. - slotFills.forEach( ( fill ) => fill.current.rerender() ); + slotFills.forEach( ( fill ) => fill.rerender() ); } }; diff --git a/packages/components/src/slot-fill/fill.ts b/packages/components/src/slot-fill/fill.ts index b11b7af09b82f0..0a31c8276b3f10 100644 --- a/packages/components/src/slot-fill/fill.ts +++ b/packages/components/src/slot-fill/fill.ts @@ -29,7 +29,7 @@ export default function Fill( { name, children }: FillComponentProps ) { useLayoutEffect( () => { ref.current.children = children; if ( slot ) { - slot.forceUpdate(); + slot.rerender(); } }, [ slot, children ] ); diff --git a/packages/components/src/slot-fill/provider.tsx b/packages/components/src/slot-fill/provider.tsx index 6ed624bab67a3c..e2b98e73e1b707 100644 --- a/packages/components/src/slot-fill/provider.tsx +++ b/packages/components/src/slot-fill/provider.tsx @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import type { Component } from '@wordpress/element'; import { useState } from '@wordpress/element'; /** @@ -11,20 +10,17 @@ import SlotFillContext from './context'; import type { FillComponentProps, BaseSlotFillContext, - BaseSlotComponentProps, SlotFillProviderProps, SlotKey, + Rerenderable, } from './types'; function createSlotRegistry(): BaseSlotFillContext { - const slots: Record< SlotKey, Component< BaseSlotComponentProps > > = {}; + const slots: Record< SlotKey, Rerenderable > = {}; const fills: Record< SlotKey, FillComponentProps[] > = {}; let listeners: Array< () => void > = []; - function registerSlot( - name: SlotKey, - slot: Component< BaseSlotComponentProps > - ) { + function registerSlot( name: SlotKey, slot: Rerenderable ) { const previousSlot = slots[ name ]; slots[ name ] = slot; triggerListeners(); @@ -38,7 +34,7 @@ function createSlotRegistry(): BaseSlotFillContext { // assigned into the instance, such that its own rendering of children // will be empty (the new Slot will subsume all fills for this name). if ( previousSlot ) { - previousSlot.forceUpdate(); + previousSlot.rerender(); } } @@ -47,10 +43,7 @@ function createSlotRegistry(): BaseSlotFillContext { forceUpdateSlot( name ); } - function unregisterSlot( - name: SlotKey, - instance: Component< BaseSlotComponentProps > - ) { + function unregisterSlot( name: SlotKey, instance: Rerenderable ) { // If a previous instance of a Slot by this name unmounts, do nothing, // as the slot and its fills should only be removed for the current // known instance. @@ -68,15 +61,13 @@ function createSlotRegistry(): BaseSlotFillContext { forceUpdateSlot( name ); } - function getSlot( - name: SlotKey - ): Component< BaseSlotComponentProps > | undefined { + function getSlot( name: SlotKey ): Rerenderable | undefined { return slots[ name ]; } function getFills( name: SlotKey, - slotInstance: Component< BaseSlotComponentProps > + slotInstance: Rerenderable ): FillComponentProps[] { // Fills should only be returned for the current instance of the slot // in which they occupy. @@ -90,7 +81,7 @@ function createSlotRegistry(): BaseSlotFillContext { const slot = getSlot( name ); if ( slot ) { - slot.forceUpdate(); + slot.rerender(); } } diff --git a/packages/components/src/slot-fill/slot.tsx b/packages/components/src/slot-fill/slot.tsx index 3fe2a549359260..fe4a741ddbfbad 100644 --- a/packages/components/src/slot-fill/slot.tsx +++ b/packages/components/src/slot-fill/slot.tsx @@ -7,8 +7,11 @@ import type { ReactElement, ReactNode, Key } from 'react'; * WordPress dependencies */ import { + useContext, + useEffect, + useReducer, + useRef, Children, - Component, cloneElement, isEmptyElement, } from '@wordpress/element'; @@ -17,7 +20,7 @@ import { * Internal dependencies */ import SlotFillContext from './context'; -import type { BaseSlotComponentProps, SlotComponentProps } from './types'; +import type { SlotComponentProps } from './types'; /** * Whether the argument is a function. @@ -29,90 +32,50 @@ function isFunction( maybeFunc: any ): maybeFunc is Function { return typeof maybeFunc === 'function'; } -class SlotComponent extends Component< BaseSlotComponentProps > { - private isUnmounted: boolean; - - constructor( props: BaseSlotComponentProps ) { - super( props ); - - this.isUnmounted = false; - } - - componentDidMount() { - const { registerSlot } = this.props; - this.isUnmounted = false; - registerSlot( this.props.name, this ); - } - - componentWillUnmount() { - const { unregisterSlot } = this.props; - this.isUnmounted = true; - unregisterSlot( this.props.name, this ); - } - - componentDidUpdate( prevProps: BaseSlotComponentProps ) { - const { name, unregisterSlot, registerSlot } = this.props; - - if ( prevProps.name !== name ) { - unregisterSlot( prevProps.name, this ); - registerSlot( name, this ); - } - } - - forceUpdate() { - if ( this.isUnmounted ) { - return; - } - super.forceUpdate(); - } - - render() { - const { children, name, fillProps = {}, getFills } = this.props; - const fills: ReactNode[] = ( getFills( name, this ) ?? [] ) - .map( ( fill ) => { - const fillChildren = isFunction( fill.children ) - ? fill.children( fillProps ) - : fill.children; - return Children.map( fillChildren, ( child, childIndex ) => { - if ( ! child || typeof child === 'string' ) { - return child; - } - let childKey: Key = childIndex; - if ( - typeof child === 'object' && - 'key' in child && - child?.key - ) { - childKey = child.key; - } - - return cloneElement( child as ReactElement, { - key: childKey, - } ); +function Slot( props: Omit< SlotComponentProps, 'bubblesVirtually' > ) { + const registry = useContext( SlotFillContext ); + const [ , rerender ] = useReducer( () => [], [] ); + const ref = useRef( { rerender } ); + + const { name, children, fillProps = {} } = props; + + useEffect( () => { + const refValue = ref.current; + registry.registerSlot( name, refValue ); + return () => registry.unregisterSlot( name, refValue ); + }, [ registry, name ] ); + + const fills: ReactNode[] = ( registry.getFills( name, ref.current ) ?? [] ) + .map( ( fill ) => { + const fillChildren = isFunction( fill.children ) + ? fill.children( fillProps ) + : fill.children; + return Children.map( fillChildren, ( child, childIndex ) => { + if ( ! child || typeof child === 'string' ) { + return child; + } + let childKey: Key = childIndex; + if ( + typeof child === 'object' && + 'key' in child && + child?.key + ) { + childKey = child.key; + } + + return cloneElement( child as ReactElement, { + key: childKey, } ); - } ) - .filter( - // In some cases fills are rendered only when some conditions apply. - // This ensures that we only use non-empty fills when rendering, i.e., - // it allows us to render wrappers only when the fills are actually present. - ( element ) => ! isEmptyElement( element ) - ); - - return <>{ isFunction( children ) ? children( fills ) : fills }; - } + } ); + } ) + .filter( + // In some cases fills are rendered only when some conditions apply. + // This ensures that we only use non-empty fills when rendering, i.e., + // it allows us to render wrappers only when the fills are actually present. + ( element ) => ! isEmptyElement( element ) + ); + + return <>{ isFunction( children ) ? children( fills ) : fills }; } -const Slot = ( props: Omit< SlotComponentProps, 'bubblesVirtually' > ) => ( - - { ( { registerSlot, unregisterSlot, getFills } ) => ( - - ) } - -); - export default Slot; diff --git a/packages/components/src/slot-fill/types.ts b/packages/components/src/slot-fill/types.ts index 7e1b8b7e1f3f9f..15f082cf3f7552 100644 --- a/packages/components/src/slot-fill/types.ts +++ b/packages/components/src/slot-fill/types.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { Component, MutableRefObject, ReactNode, RefObject } from 'react'; +import type { ReactNode, RefObject } from 'react'; /** * WordPress dependencies @@ -108,42 +108,17 @@ export type SlotFillProviderProps = { passthrough?: boolean; }; -export type SlotFillBubblesVirtuallySlotRef = RefObject< HTMLElement >; -export type SlotFillBubblesVirtuallyFillRef = MutableRefObject< { - rerender: () => void; -} >; +export type SlotRef = RefObject< HTMLElement >; +export type Rerenderable = { rerender: () => void }; export type SlotFillBubblesVirtuallyContext = { - slots: ObservableMap< - SlotKey, - { - ref: SlotFillBubblesVirtuallySlotRef; - fillProps: FillProps; - } - >; - fills: ObservableMap< SlotKey, SlotFillBubblesVirtuallyFillRef[] >; - registerSlot: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallySlotRef, - fillProps: FillProps - ) => void; - unregisterSlot: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallySlotRef - ) => void; - updateSlot: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallySlotRef, - fillProps: FillProps - ) => void; - registerFill: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallyFillRef - ) => void; - unregisterFill: ( - name: SlotKey, - ref: SlotFillBubblesVirtuallyFillRef - ) => void; + slots: ObservableMap< SlotKey, { ref: SlotRef; fillProps: FillProps } >; + fills: ObservableMap< SlotKey, Rerenderable[] >; + 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; /** * This helps the provider know if it's using the default context value or not. @@ -152,30 +127,14 @@ export type SlotFillBubblesVirtuallyContext = { }; export type BaseSlotFillContext = { - registerSlot: ( - name: SlotKey, - slot: Component< BaseSlotComponentProps > - ) => void; - unregisterSlot: ( - name: SlotKey, - slot: Component< BaseSlotComponentProps > - ) => void; + registerSlot: ( name: SlotKey, slot: Rerenderable ) => void; + unregisterSlot: ( name: SlotKey, slot: Rerenderable ) => void; registerFill: ( name: SlotKey, instance: FillComponentProps ) => void; unregisterFill: ( name: SlotKey, instance: FillComponentProps ) => void; - getSlot: ( - name: SlotKey - ) => Component< BaseSlotComponentProps > | undefined; + getSlot: ( name: SlotKey ) => Rerenderable | undefined; getFills: ( name: SlotKey, - slotInstance: Component< BaseSlotComponentProps > + slotInstance: Rerenderable ) => FillComponentProps[]; subscribe: ( listener: () => void ) => () => void; }; - -export type BaseSlotComponentProps = Pick< - BaseSlotFillContext, - 'registerSlot' | 'unregisterSlot' | 'getFills' -> & - Omit< SlotComponentProps, 'bubblesVirtually' > & { - children?: ( fills: ReactNode ) => ReactNode; - }; From c38610ae21d5aad266aff9a08fc49e80cd38f0e6 Mon Sep 17 00:00:00 2001 From: louwie17 Date: Wed, 20 Nov 2024 11:42:10 -0400 Subject: [PATCH 184/605] DataForm: enable fields to declare a different layout (#66531) Co-authored-by: louwie17 Co-authored-by: oandregal Co-authored-by: gigitux Co-authored-by: youknowriad --- .../dataform-combined-edit/index.tsx | 69 ----- .../dataform-combined-edit/style.scss | 16 - .../src/components/dataform-context/index.tsx | 30 ++ .../src/components/dataform/index.tsx | 27 +- .../dataform/stories/index.story.tsx | 137 +++++--- .../dataforms-layouts/data-form-layout.tsx | 87 ++++++ .../dataforms-layouts/get-visible-fields.ts | 29 -- .../dataviews/src/dataforms-layouts/index.tsx | 14 +- .../dataforms-layouts/is-combined-field.ts | 10 + .../src/dataforms-layouts/panel/index.tsx | 293 ++++++++++++------ .../src/dataforms-layouts/regular/index.tsx | 138 ++++++--- .../src/dataforms-layouts/regular/style.scss | 30 ++ packages/dataviews/src/normalize-fields.ts | 34 +- .../dataviews/src/normalize-form-fields.ts | 42 +++ packages/dataviews/src/style.scss | 2 +- packages/dataviews/src/types.ts | 44 +-- packages/dataviews/src/validation.ts | 2 +- .../src/components/post-edit/index.js | 20 +- 18 files changed, 645 insertions(+), 379 deletions(-) delete mode 100644 packages/dataviews/src/components/dataform-combined-edit/index.tsx delete mode 100644 packages/dataviews/src/components/dataform-combined-edit/style.scss create mode 100644 packages/dataviews/src/components/dataform-context/index.tsx create mode 100644 packages/dataviews/src/dataforms-layouts/data-form-layout.tsx delete mode 100644 packages/dataviews/src/dataforms-layouts/get-visible-fields.ts create mode 100644 packages/dataviews/src/dataforms-layouts/is-combined-field.ts create mode 100644 packages/dataviews/src/dataforms-layouts/regular/style.scss create mode 100644 packages/dataviews/src/normalize-form-fields.ts diff --git a/packages/dataviews/src/components/dataform-combined-edit/index.tsx b/packages/dataviews/src/components/dataform-combined-edit/index.tsx deleted file mode 100644 index 90a92ac861bdd1..00000000000000 --- a/packages/dataviews/src/components/dataform-combined-edit/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/** - * WordPress dependencies - */ -import { - __experimentalHStack as HStack, - __experimentalVStack as VStack, - __experimentalHeading as Heading, - __experimentalSpacer as Spacer, -} from '@wordpress/components'; - -/** - * Internal dependencies - */ -import type { DataFormCombinedEditProps, NormalizedField } from '../../types'; -import FormFieldVisibility from '../form-field-visibility'; - -function Header( { title }: { title: string } ) { - return ( - - - - { title } - - - - - ); -} - -function DataFormCombinedEdit< Item >( { - field, - data, - onChange, - hideLabelFromVision, -}: DataFormCombinedEditProps< Item > ) { - const className = 'dataforms-combined-edit'; - const visibleChildren = ( field.children ?? [] ) - .map( ( fieldId ) => field.fields.find( ( { id } ) => id === fieldId ) ) - .filter( - ( childField ): childField is NormalizedField< Item > => - !! childField - ); - const children = visibleChildren.map( ( child ) => { - return ( - -
- -
-
- ); - } ); - - const Stack = field.direction === 'horizontal' ? HStack : VStack; - - return ( - <> - { ! hideLabelFromVision &&
} - - { children } - - - ); -} - -export default DataFormCombinedEdit; diff --git a/packages/dataviews/src/components/dataform-combined-edit/style.scss b/packages/dataviews/src/components/dataform-combined-edit/style.scss deleted file mode 100644 index 97e052ed897989..00000000000000 --- a/packages/dataviews/src/components/dataform-combined-edit/style.scss +++ /dev/null @@ -1,16 +0,0 @@ -.dataforms-layouts-panel__field-dropdown { - .dataforms-combined-edit { - border: none; - padding: 0; - } -} - -.dataforms-combined-edit { - &__field { - flex: 1 1 auto; - } - - p.components-base-control__help:has(.components-checkbox-control__help) { - margin-top: $grid-unit-05; - } -} diff --git a/packages/dataviews/src/components/dataform-context/index.tsx b/packages/dataviews/src/components/dataform-context/index.tsx new file mode 100644 index 00000000000000..72fbf7e0f42ab6 --- /dev/null +++ b/packages/dataviews/src/components/dataform-context/index.tsx @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import type { NormalizedField } from '../../types'; + +type DataFormContextType< Item > = { + fields: NormalizedField< Item >[]; +}; + +const DataFormContext = createContext< DataFormContextType< any > >( { + fields: [], +} ); + +export function DataFormProvider< Item >( { + fields, + children, +}: React.PropsWithChildren< { fields: NormalizedField< Item >[] } > ) { + return ( + + { children } + + ); +} + +export default DataFormContext; diff --git a/packages/dataviews/src/components/dataform/index.tsx b/packages/dataviews/src/components/dataform/index.tsx index 58f0bf06afb414..b359ddba74381e 100644 --- a/packages/dataviews/src/components/dataform/index.tsx +++ b/packages/dataviews/src/components/dataform/index.tsx @@ -1,17 +1,34 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; + /** * Internal dependencies */ import type { DataFormProps } from '../../types'; -import { getFormLayout } from '../../dataforms-layouts'; +import { DataFormProvider } from '../dataform-context'; +import { normalizeFields } from '../../normalize-fields'; +import { DataFormLayout } from '../../dataforms-layouts/data-form-layout'; export default function DataForm< Item >( { + data, form, - ...props + fields, + onChange, }: DataFormProps< Item > ) { - const layout = getFormLayout( form.type ?? 'regular' ); - if ( ! layout ) { + const normalizedFields = useMemo( + () => normalizeFields( fields ), + [ fields ] + ); + + if ( ! form.fields ) { return null; } - return ; + return ( + + + + ); } diff --git a/packages/dataviews/src/components/dataform/stories/index.story.tsx b/packages/dataviews/src/components/dataform/stories/index.story.tsx index b59d79063200bf..ecad2af43fb84b 100644 --- a/packages/dataviews/src/components/dataform/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataform/stories/index.story.tsx @@ -1,13 +1,14 @@ /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useMemo, useState } from '@wordpress/element'; +import { ToggleControl } from '@wordpress/components'; /** * Internal dependencies */ import DataForm from '../index'; -import type { CombinedFormField, Field } from '../../../types'; +import type { Field, Form } from '../../../types'; type SamplePost = { title: string; @@ -27,8 +28,13 @@ const meta = { type: { control: { type: 'select' }, description: - 'Chooses the layout of the form. "regular" is the default layout.', - options: [ 'regular', 'panel' ], + 'Chooses the default layout of each field. "regular" is the default layout.', + options: [ 'default', 'regular', 'panel' ], + }, + labelPosition: { + control: { type: 'select' }, + description: 'Chooses the label position of the layout.', + options: [ 'default', 'top', 'side', 'none' ], }, }, }; @@ -97,9 +103,33 @@ const fields = [ return item.status !== 'private'; }, }, + { + id: 'sticky', + label: 'Sticky', + type: 'integer', + Edit: ( { field, onChange, data, hideLabelFromVision } ) => { + const { id, getValue } = field; + return ( + + onChange( { [ id ]: ! getValue( { item: data } ) } ) + } + /> + ); + }, + }, ] as Field< SamplePost >[]; -export const Default = ( { type }: { type: 'panel' | 'regular' } ) => { +export const Default = ( { + type, + labelPosition, +}: { + type: 'default' | 'regular' | 'panel'; + labelPosition: 'default' | 'top' | 'side' | 'none'; +} ) => { const [ post, setPost ] = useState( { title: 'Hello, World!', order: 2, @@ -108,29 +138,36 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => { reviewer: 'fulano', date: '2021-01-01T12:00:00', birthdate: '1950-02-23T12:00:00', + sticky: false, } ); - const form = { - fields: [ - 'title', - 'order', - 'author', - 'reviewer', - 'status', - 'password', - 'date', - 'birthdate', - ], - }; + const form = useMemo( + () => ( { + type, + labelPosition, + fields: [ + 'title', + 'order', + { + id: 'sticky', + layout: 'regular', + labelPosition: 'side', + }, + 'author', + 'reviewer', + 'password', + 'date', + 'birthdate', + ], + } ), + [ type, labelPosition ] + ) as Form; return ( data={ post } fields={ fields } - form={ { - ...form, - type, - } } + form={ form } onChange={ ( edits ) => setPost( ( prev ) => ( { ...prev, @@ -142,40 +179,45 @@ export const Default = ( { type }: { type: 'panel' | 'regular' } ) => { }; const CombinedFieldsComponent = ( { - type = 'regular', - combinedFieldDirection = 'vertical', + type, + labelPosition, }: { - type: 'panel' | 'regular'; - combinedFieldDirection: 'vertical' | 'horizontal'; + type: 'default' | 'regular' | 'panel'; + labelPosition: 'default' | 'top' | 'side' | 'none'; } ) => { - const [ post, setPost ] = useState( { + const [ post, setPost ] = useState< SamplePost >( { title: 'Hello, World!', order: 2, author: 1, status: 'draft', + reviewer: 'fulano', + date: '2021-01-01T12:00:00', + birthdate: '1950-02-23T12:00:00', } ); - const form = { - fields: [ 'title', 'status_and_visibility', 'order', 'author' ], - combinedFields: [ - { - id: 'status_and_visibility', - label: 'Status & Visibility', - children: [ 'status', 'password' ], - direction: combinedFieldDirection, - render: ( { item } ) => item.status, - }, - ] as CombinedFormField< any >[], - }; + const form = useMemo( + () => ( { + type, + labelPosition, + fields: [ + 'title', + { + id: 'status', + label: 'Status & Visibility', + children: [ 'status', 'password' ], + }, + 'order', + 'author', + ], + } ), + [ type, labelPosition ] + ) as Form; return ( - data={ post } fields={ fields } - form={ { - ...form, - type, - } } + form={ form } onChange={ ( edits ) => setPost( ( prev ) => ( { ...prev, @@ -191,11 +233,8 @@ export const CombinedFields = { render: CombinedFieldsComponent, argTypes: { ...meta.argTypes, - combinedFieldDirection: { - control: { type: 'select' }, - description: - 'Chooses the direction of the combined field. "vertical" is the default layout.', - options: [ 'vertical', 'horizontal' ], - }, + }, + args: { + type: 'panel', }, }; diff --git a/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx new file mode 100644 index 00000000000000..08cc47f569eafe --- /dev/null +++ b/packages/dataviews/src/dataforms-layouts/data-form-layout.tsx @@ -0,0 +1,87 @@ +/** + * WordPress dependencies + */ +import { __experimentalVStack as VStack } from '@wordpress/components'; +import { useContext, useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import type { Form, FormField, SimpleFormField } from '../types'; +import { getFormFieldLayout } from './index'; +import DataFormContext from '../components/dataform-context'; +import { isCombinedField } from './is-combined-field'; +import normalizeFormFields from '../normalize-form-fields'; + +export function DataFormLayout< Item >( { + data, + form, + onChange, + children, +}: { + data: Item; + form: Form; + onChange: ( value: any ) => void; + children?: ( + FieldLayout: ( props: { + data: Item; + field: FormField; + onChange: ( value: any ) => void; + hideLabelFromVision?: boolean; + } ) => React.JSX.Element | null, + field: FormField + ) => React.JSX.Element; +} ) { + const { fields: fieldDefinitions } = useContext( DataFormContext ); + + function getFieldDefinition( field: SimpleFormField | string ) { + const fieldId = typeof field === 'string' ? field : field.id; + + return fieldDefinitions.find( + ( fieldDefinition ) => fieldDefinition.id === fieldId + ); + } + + const normalizedFormFields = useMemo( + () => normalizeFormFields( form ), + [ form ] + ); + + return ( + + { normalizedFormFields.map( ( formField ) => { + const FieldLayout = getFormFieldLayout( formField.layout ) + ?.component; + + if ( ! FieldLayout ) { + return null; + } + + const fieldDefinition = ! isCombinedField( formField ) + ? getFieldDefinition( formField ) + : undefined; + + if ( + fieldDefinition && + fieldDefinition.isVisible && + ! fieldDefinition.isVisible( data ) + ) { + return null; + } + + if ( children ) { + return children( FieldLayout, formField ); + } + + return ( + + ); + } ) } + + ); +} diff --git a/packages/dataviews/src/dataforms-layouts/get-visible-fields.ts b/packages/dataviews/src/dataforms-layouts/get-visible-fields.ts deleted file mode 100644 index d95d59a88394e4..00000000000000 --- a/packages/dataviews/src/dataforms-layouts/get-visible-fields.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Internal dependencies - */ -import { normalizeCombinedFields } from '../normalize-fields'; -import type { - Field, - CombinedFormField, - NormalizedCombinedFormField, -} from '../types'; - -export function getVisibleFields< Item >( - fields: Field< Item >[], - formFields: string[] = [], - combinedFields?: CombinedFormField< Item >[] -): Field< Item >[] { - const visibleFields: Array< - Field< Item > | NormalizedCombinedFormField< Item > - > = [ ...fields ]; - if ( combinedFields ) { - visibleFields.push( - ...normalizeCombinedFields( combinedFields, fields ) - ); - } - return formFields - .map( ( fieldId ) => - visibleFields.find( ( { id } ) => id === fieldId ) - ) - .filter( ( field ): field is Field< Item > => !! field ); -} diff --git a/packages/dataviews/src/dataforms-layouts/index.tsx b/packages/dataviews/src/dataforms-layouts/index.tsx index 9434ea724ed4ca..5e4f3617d9c7dd 100644 --- a/packages/dataviews/src/dataforms-layouts/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/index.tsx @@ -1,20 +1,20 @@ /** * Internal dependencies */ -import FormRegular from './regular'; -import FormPanel from './panel'; +import FormRegularField from './regular'; +import FormPanelField from './panel'; -const FORM_LAYOUTS = [ +const FORM_FIELD_LAYOUTS = [ { type: 'regular', - component: FormRegular, + component: FormRegularField, }, { type: 'panel', - component: FormPanel, + component: FormPanelField, }, ]; -export function getFormLayout( type: string ) { - return FORM_LAYOUTS.find( ( layout ) => layout.type === type ); +export function getFormFieldLayout( type: string ) { + return FORM_FIELD_LAYOUTS.find( ( layout ) => layout.type === type ); } diff --git a/packages/dataviews/src/dataforms-layouts/is-combined-field.ts b/packages/dataviews/src/dataforms-layouts/is-combined-field.ts new file mode 100644 index 00000000000000..3df6fdc60f906e --- /dev/null +++ b/packages/dataviews/src/dataforms-layouts/is-combined-field.ts @@ -0,0 +1,10 @@ +/** + * Internal dependencies + */ +import type { FormField, CombinedFormField } from '../types'; + +export function isCombinedField( + field: FormField +): field is CombinedFormField { + return ( field as CombinedFormField ).children !== undefined; +} diff --git a/packages/dataviews/src/dataforms-layouts/panel/index.tsx b/packages/dataviews/src/dataforms-layouts/panel/index.tsx index b74e5e4667d4b1..269b2bb418a856 100644 --- a/packages/dataviews/src/dataforms-layouts/panel/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/panel/index.tsx @@ -9,29 +9,29 @@ import { Dropdown, Button, } from '@wordpress/components'; -import { useState, useMemo } from '@wordpress/element'; import { sprintf, __, _x } from '@wordpress/i18n'; +import { useState, useMemo, useContext } from '@wordpress/element'; import { closeSmall } from '@wordpress/icons'; /** * Internal dependencies */ -import { normalizeFields } from '../../normalize-fields'; -import { getVisibleFields } from '../get-visible-fields'; -import type { DataFormProps, NormalizedField } from '../../types'; -import FormFieldVisibility from '../../components/form-field-visibility'; - -interface FormFieldProps< Item > { - data: Item; - field: NormalizedField< Item >; - onChange: ( value: any ) => void; -} +import type { + Form, + FormField, + FieldLayoutProps, + NormalizedField, + SimpleFormField, +} from '../../types'; +import DataFormContext from '../../components/dataform-context'; +import { DataFormLayout } from '../data-form-layout'; +import { isCombinedField } from '../is-combined-field'; function DropdownHeader( { title, onClose, }: { - title: string; + title?: string; onClose: () => void; } ) { return ( @@ -40,9 +40,11 @@ function DropdownHeader( { spacing={ 4 } > - - { title } - + { title && ( + + { title } + + ) } { onClose && ( + ( + + ) } + renderContent={ ( { onClose } ) => ( + <> + + + { ( FieldLayout, nestedField ) => ( + - - ) } - /> -
- + ) } + + + ) } + /> ); } -export default function FormPanel< Item >( { +export default function FormPanelField< Item >( { data, - fields, - form, + field, onChange, -}: DataFormProps< Item > ) { - const visibleFields = useMemo( - () => - normalizeFields( - getVisibleFields< Item >( - fields, - form.fields, - form.combinedFields - ) - ), - [ fields, form.fields, form.combinedFields ] +}: FieldLayoutProps< Item > ) { + const { fields } = useContext( DataFormContext ); + const fieldDefinition = fields.find( ( fieldDef ) => { + // Default to the first child if it is a combined field. + if ( isCombinedField( field ) ) { + const children = field.children.filter( + ( child ): child is string | SimpleFormField => + typeof child === 'string' || ! isCombinedField( child ) + ); + const firstChildFieldId = + typeof children[ 0 ] === 'string' + ? children[ 0 ] + : children[ 0 ].id; + return fieldDef.id === firstChildFieldId; + } + return fieldDef.id === field.id; + } ); + const labelPosition = field.labelPosition ?? 'side'; + + // Use internal state instead of a ref to make sure that the component + // re-renders when the popover's anchor updates. + const [ popoverAnchor, setPopoverAnchor ] = useState< HTMLElement | null >( + null ); - return ( - - { visibleFields.map( ( field ) => { - return ( - +
+ { fieldLabel } +
+
+ - - - ); - } ) } - + popoverAnchor={ popoverAnchor } + fieldDefinition={ fieldDefinition } + data={ data } + onChange={ onChange } + labelPosition={ labelPosition } + /> +
+
+ ); + } + + if ( labelPosition === 'none' ) { + return ( +
+ +
+ ); + } + + // Defaults to label position side. + return ( + +
+ { fieldLabel } +
+
+ +
+
); } diff --git a/packages/dataviews/src/dataforms-layouts/regular/index.tsx b/packages/dataviews/src/dataforms-layouts/regular/index.tsx index 6a340a50584df4..a3d90b807b5cd4 100644 --- a/packages/dataviews/src/dataforms-layouts/regular/index.tsx +++ b/packages/dataviews/src/dataforms-layouts/regular/index.tsx @@ -1,52 +1,116 @@ /** * WordPress dependencies */ -import { __experimentalVStack as VStack } from '@wordpress/components'; -import { useMemo } from '@wordpress/element'; +import { useContext, useMemo } from '@wordpress/element'; +import { + __experimentalHStack as HStack, + __experimentalVStack as VStack, + __experimentalHeading as Heading, + __experimentalSpacer as Spacer, +} from '@wordpress/components'; /** * Internal dependencies */ -import { normalizeFields } from '../../normalize-fields'; -import { getVisibleFields } from '../get-visible-fields'; -import type { DataFormProps } from '../../types'; -import FormFieldVisibility from '../../components/form-field-visibility'; +import type { Form, FieldLayoutProps } from '../../types'; +import DataFormContext from '../../components/dataform-context'; +import { DataFormLayout } from '../data-form-layout'; +import { isCombinedField } from '../is-combined-field'; -export default function FormRegular< Item >( { +function Header( { title }: { title: string } ) { + return ( + + + + { title } + + + + + ); +} + +export default function FormRegularField< Item >( { data, - fields, - form, + field, onChange, -}: DataFormProps< Item > ) { - const visibleFields = useMemo( - () => - normalizeFields( - getVisibleFields< Item >( - fields, - form.fields, - form.combinedFields - ) - ), - [ fields, form.fields, form.combinedFields ] + hideLabelFromVision, +}: FieldLayoutProps< Item > ) { + const { fields } = useContext( DataFormContext ); + + const form = useMemo( () => { + if ( isCombinedField( field ) ) { + return { + fields: field.children.map( ( child ) => { + if ( typeof child === 'string' ) { + return { + id: child, + }; + } + return child; + } ), + type: 'regular' as const, + }; + } + + return { + type: 'regular' as const, + fields: [], + }; + }, [ field ] ); + + if ( isCombinedField( field ) ) { + return ( + <> + { ! hideLabelFromVision && field.label && ( +
+ ) } + + + ); + } + + const labelPosition = field.labelPosition ?? 'top'; + const fieldDefinition = fields.find( + ( fieldDef ) => fieldDef.id === field.id ); - return ( - - { visibleFields.map( ( field ) => { - return ( - +
+ { fieldDefinition.label } +
+
+ - - - ); - } ) } - + field={ fieldDefinition } + onChange={ onChange } + hideLabelFromVision + /> +
+ + ); + } + + return ( +
+ +
); } diff --git a/packages/dataviews/src/dataforms-layouts/regular/style.scss b/packages/dataviews/src/dataforms-layouts/regular/style.scss new file mode 100644 index 00000000000000..d94b804fdf1fe5 --- /dev/null +++ b/packages/dataviews/src/dataforms-layouts/regular/style.scss @@ -0,0 +1,30 @@ +.dataforms-layouts-regular__field { + width: 100%; + min-height: $grid-unit-40; + justify-content: flex-start !important; + align-items: flex-start !important; +} + +.dataforms-layouts-regular__field .components-base-control__label { + font-size: inherit; + font-weight: normal; + text-transform: none; +} + +.dataforms-layouts-regular__field-label { + width: 38%; + flex-shrink: 0; + min-height: $grid-unit-40; + display: flex; + align-items: center; + padding: 6px 0; // Matches button to ensure alignment + line-height: $grid-unit-05 * 5; + hyphens: auto; +} + +.dataforms-layouts-regular__field-control { + flex-grow: 1; + min-height: $grid-unit-40; + display: flex; + align-items: center; +} diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index 562f29fcce84fe..2ed87cbe112229 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -2,14 +2,8 @@ * Internal dependencies */ import getFieldTypeDefinition from './field-types'; -import type { - CombinedFormField, - Field, - NormalizedField, - NormalizedCombinedFormField, -} from './types'; +import type { Field, NormalizedField } from './types'; import { getControl } from './dataform-controls'; -import DataFormCombinedEdit from './components/dataform-combined-edit'; const getValueFromId = ( id: string ) => @@ -87,29 +81,3 @@ export function normalizeFields< Item >( }; } ); } - -/** - * Apply default values and normalize the fields config. - * - * @param combinedFields combined field list. - * @param fields Fields config. - * @return Normalized fields config. - */ -export function normalizeCombinedFields< Item >( - combinedFields: CombinedFormField< Item >[], - fields: Field< Item >[] -): NormalizedCombinedFormField< Item >[] { - return combinedFields.map( ( combinedField ) => { - return { - ...combinedField, - Edit: DataFormCombinedEdit, - fields: normalizeFields( - combinedField.children - .map( ( fieldId ) => - fields.find( ( { id } ) => id === fieldId ) - ) - .filter( ( field ): field is Field< Item > => !! field ) - ), - }; - } ); -} diff --git a/packages/dataviews/src/normalize-form-fields.ts b/packages/dataviews/src/normalize-form-fields.ts new file mode 100644 index 00000000000000..3cd5f67564d7ce --- /dev/null +++ b/packages/dataviews/src/normalize-form-fields.ts @@ -0,0 +1,42 @@ +/** + * Internal dependencies + */ +import type { Form } from './types'; + +interface NormalizedFormField { + id: string; + layout: 'regular' | 'panel'; + labelPosition: 'side' | 'top' | 'none'; +} + +export default function normalizeFormFields( + form: Form +): NormalizedFormField[] { + let layout: 'regular' | 'panel' = 'regular'; + if ( [ 'regular', 'panel' ].includes( form.type ?? '' ) ) { + layout = form.type as 'regular' | 'panel'; + } + + const labelPosition = + form.labelPosition ?? ( layout === 'regular' ? 'top' : 'side' ); + + return ( form.fields ?? [] ).map( ( field ) => { + if ( typeof field === 'string' ) { + return { + id: field, + layout, + labelPosition, + }; + } + + const fieldLayout = field.layout ?? layout; + const fieldLabelPosition = + field.labelPosition ?? + ( fieldLayout === 'regular' ? 'top' : 'side' ); + return { + ...field, + layout: fieldLayout, + labelPosition: fieldLabelPosition, + }; + } ); +} diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index 26c6ecea645f43..5639f3cac0da51 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -6,7 +6,6 @@ @import "./components/dataviews-item-actions/style.scss"; @import "./components/dataviews-selection-checkbox/style.scss"; @import "./components/dataviews-view-config/style.scss"; -@import "./components/dataform-combined-edit/style.scss"; @import "./dataviews-layouts/grid/style.scss"; @import "./dataviews-layouts/list/style.scss"; @@ -14,3 +13,4 @@ @import "./dataform-controls/style.scss"; @import "./dataforms-layouts/panel/style.scss"; +@import "./dataforms-layouts/regular/style.scss"; diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 71990f72d4eecd..8c4276f2541ecc 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -527,37 +527,41 @@ export interface SupportedLayouts { table?: Omit< ViewTable, 'type' >; } -export interface CombinedFormField< Item > extends CombinedField { - render?: ComponentType< { item: Item } >; -} - -export interface DataFormCombinedEditProps< Item > { - field: NormalizedCombinedFormField< Item >; - data: Item; - onChange: ( value: Record< string, any > ) => void; - hideLabelFromVision?: boolean; -} +export type SimpleFormField = { + id: string; + layout?: 'regular' | 'panel'; + labelPosition?: 'side' | 'top' | 'none'; +}; -export type NormalizedCombinedFormField< Item > = CombinedFormField< Item > & { - fields: NormalizedField< Item >[]; - Edit?: ComponentType< DataFormCombinedEditProps< Item > >; +export type CombinedFormField = { + id: string; + label?: string; + layout?: 'regular' | 'panel'; + labelPosition?: 'side' | 'top' | 'none'; + children: Array< FormField | string >; }; +export type FormField = SimpleFormField | CombinedFormField; + /** * The form configuration. */ -export type Form< Item > = { +export type Form = { type?: 'regular' | 'panel'; - fields?: string[]; - /** - * The fields to combine. - */ - combinedFields?: CombinedFormField< Item >[]; + fields?: Array< FormField | string >; + labelPosition?: 'side' | 'top' | 'none'; }; export interface DataFormProps< Item > { data: Item; fields: Field< Item >[]; - form: Form< Item >; + form: Form; onChange: ( value: Record< string, any > ) => void; } + +export interface FieldLayoutProps< Item > { + data: Item; + field: FormField; + onChange: ( value: any ) => void; + hideLabelFromVision?: boolean; +} diff --git a/packages/dataviews/src/validation.ts b/packages/dataviews/src/validation.ts index 0a6542da4e8d40..bcc9a15908ff59 100644 --- a/packages/dataviews/src/validation.ts +++ b/packages/dataviews/src/validation.ts @@ -16,7 +16,7 @@ import type { Field, Form } from './types'; export function isItemValid< Item >( item: Item, fields: Field< Item >[], - form: Form< Item > + form: Form ): boolean { const _fields = normalizeFields( fields.filter( ( { id } ) => !! form.fields?.includes( id ) ) diff --git a/packages/edit-site/src/components/post-edit/index.js b/packages/edit-site/src/components/post-edit/index.js index a535eef4ce7878..a7842f0feb3c2f 100644 --- a/packages/edit-site/src/components/post-edit/index.js +++ b/packages/edit-site/src/components/post-edit/index.js @@ -70,9 +70,16 @@ function PostEditForm( { postType, postId } ) { () => ( { type: 'panel', fields: [ - 'featured_media', + { + id: 'featured_media', + layout: 'regular', + }, 'title', - 'status_and_visibility', + { + id: 'status', + label: __( 'Status & Visibility' ), + children: [ 'status', 'password' ], + }, 'author', 'date', 'slug', @@ -83,15 +90,6 @@ function PostEditForm( { postType, postId } ) { ids.length === 1 || fieldsWithBulkEditSupport.includes( field ) ), - combinedFields: [ - { - id: 'status_and_visibility', - label: __( 'Status & Visibility' ), - children: [ 'status', 'password' ], - direction: 'vertical', - render: ( { item } ) => item.status, - }, - ], } ), [ ids ] ); From 776b9e0da9709e360a2cd95112a811ccefbfa818 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 20 Nov 2024 10:01:14 -0600 Subject: [PATCH 185/605] Preserve footer template bar in zoom out (#67135) --- .../editor/src/components/editor-interface/index.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/editor/src/components/editor-interface/index.js b/packages/editor/src/components/editor-interface/index.js index 3692a5ed797f5e..6f6ffbec7b9c32 100644 --- a/packages/editor/src/components/editor-interface/index.js +++ b/packages/editor/src/components/editor-interface/index.js @@ -10,11 +10,7 @@ import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; import { useSelect } from '@wordpress/data'; import { __, _x } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; -import { - store as blockEditorStore, - BlockBreadcrumb, - BlockToolbar, -} from '@wordpress/block-editor'; +import { BlockBreadcrumb, BlockToolbar } from '@wordpress/block-editor'; import { useViewportMatch } from '@wordpress/compose'; import { useState, useCallback } from '@wordpress/element'; @@ -31,8 +27,6 @@ import TextEditor from '../text-editor'; import VisualEditor from '../visual-editor'; import EditorContentSlotFill from './content-slot-fill'; -import { unlock } from '../../lock-unlock'; - const interfaceLabels = { /* translators: accessibility text for the editor top bar landmark region. */ header: __( 'Editor top bar' ), @@ -69,13 +63,11 @@ export default function EditorInterface( { isPreviewMode, showBlockBreadcrumbs, documentLabel, - isZoomOut, } = useSelect( ( select ) => { const { get } = select( preferencesStore ); const { getEditorSettings, getPostTypeLabel } = select( editorStore ); const editorSettings = getEditorSettings(); const postTypeLabel = getPostTypeLabel(); - const { isZoomOut: _isZoomOut } = unlock( select( blockEditorStore ) ); return { mode: select( editorStore ).getEditorMode(), @@ -88,7 +80,6 @@ export default function EditorInterface( { documentLabel: // translators: Default label for the Document in the Block Breadcrumb. postTypeLabel || _x( 'Document', 'noun, breadcrumb' ), - isZoomOut: _isZoomOut(), }; }, [] ); const isLargeViewport = useViewportMatch( 'medium' ); @@ -197,7 +188,6 @@ export default function EditorInterface( { isLargeViewport && showBlockBreadcrumbs && isRichEditingEnabled && - ! isZoomOut && mode === 'visual' && ( ) From fa675a794311e9c4d27cbb9d51ac95dcac08b1cc Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 20 Nov 2024 10:02:18 -0600 Subject: [PATCH 186/605] Leave help text in patterns flyout regardless of zoom state (#67132) --- .../pattern-category-previews.js | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js index a19a579ae5c0cf..c6ce9ba97d2501 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js @@ -17,7 +17,6 @@ import { __experimentalText as Text, FlexBlock, } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -34,8 +33,6 @@ import { starterPatternsCategory, INSERTER_PATTERN_TYPES, } from './utils'; -import { store as blockEditorStore } from '../../../store'; -import { unlock } from '../../../lock-unlock'; const noop = () => {}; @@ -46,10 +43,6 @@ export function PatternCategoryPreviews( { category, showTitlesAsTooltip, } ) { - const isZoomOutMode = useSelect( - ( select ) => unlock( select( blockEditorStore ) ).isZoomOut(), - [] - ); const [ allPatterns, , onClickPattern ] = usePatternsState( onInsert, rootClientId, @@ -179,15 +172,13 @@ export function PatternCategoryPreviews( {
{ currentCategoryPatterns.length > 0 && ( <> - { isZoomOutMode && ( - - { __( 'Drag and drop patterns into the canvas.' ) } - - ) } + + { __( 'Drag and drop patterns into the canvas.' ) } + Date: Wed, 20 Nov 2024 17:16:49 +0000 Subject: [PATCH 187/605] Bump plugin version to 19.7.0 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 738fe1a3bd5268..3e4d6941b5a0f3 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.6 * Requires PHP: 7.2 - * Version: 19.7.0-rc.2 + * Version: 19.7.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 376cd0b9e6edf6..04a8d2021f12a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "19.7.0-rc.2", + "version": "19.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "19.7.0-rc.2", + "version": "19.7.0", "hasInstallScript": true, "license": "GPL-2.0-or-later", "workspaces": [ diff --git a/package.json b/package.json index a7470a1333ffad..52668f667c71cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "19.7.0-rc.2", + "version": "19.7.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From e4a716eb524458e2c567c5fd5f92d1985d7ea779 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:27:06 -0500 Subject: [PATCH 188/605] Add documentation about required Core changes when updating minimum WordPress version (#67167) * Add a note about required Core SVN changes. * A few refinements. --------- Co-authored-by: desrosj Co-authored-by: ndiego Co-authored-by: Mamaduka --- docs/explanations/architecture/performance.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/explanations/architecture/performance.md b/docs/explanations/architecture/performance.md index 4c8b6386b9263b..8c1034ad9de331 100644 --- a/docs/explanations/architecture/performance.md +++ b/docs/explanations/architecture/performance.md @@ -84,6 +84,12 @@ The new reference commit hash that is chosen needs to meet the following require - Be compatible with the new WP version used in the "Tested up to" flag. - Is already tracked on "codevitals.run" for all existing metrics. +When releasing a plugin update with changes to the minimum WordPress version requirements, the end-to-end test GitHub Action workflow in Core SVN will need to be updated for any branch losing support. Otherwise the first run of that workflow on that branch following the release will fail. + +The version of the plugin used in the workflow can be pinned by adding the `gutenberg-version` input to the test matrix. [Core-59221](https://core.trac.wordpress.org/changeset/59221) is an example of this change for the 6.4 branch. + +**Note:** Always use the final release including bug fixes (ie. `x.y.2` or `x.y.3`). If the final release is not yet known, create a [Trac ticket](https://core.trac.wordpress.org/ticket/62488) so it's not forgotten. + **A simple way to choose commit is to pick a very recent commit on trunk with a passing performance job.** ## Going further From e79c46950cd58f1588dbef531c143186e0666110 Mon Sep 17 00:00:00 2001 From: Vipul Gupta <55375170+vipul0425@users.noreply.github.com> Date: Wed, 20 Nov 2024 23:06:38 +0530 Subject: [PATCH 189/605] ToggleGroupControl : Deprecate 36px default size (#66747) * feat: Adds deprecation warning in toggleGroupControl Block. * feat: Adds the test case and story book modification. * docs: Adds changelog. * Add prop to suppress redundant warning * Updates the changelog and ToolsPanel Story. * Updates the snapshots. * Add prop to all instances in the ToolsPanel stories * Rebase changelog again --------- Co-authored-by: vipul0425 Co-authored-by: mirka <0mirka00@git.wordpress.org> --- packages/components/CHANGELOG.md | 1 + .../font-size-picker-toggle-group.tsx | 1 + .../stories/index.story.tsx | 1 + .../test/__snapshots__/index.tsx.snap | 24 +++++++++---------- .../src/toggle-group-control/test/index.tsx | 8 ++++++- .../README.md | 2 +- .../component.tsx | 2 +- .../toggle-group-control-option/README.md | 2 +- .../toggle-group-control-option/component.tsx | 1 + .../toggle-group-control/README.md | 3 ++- .../toggle-group-control/component.tsx | 10 ++++++++ .../src/toggle-group-control/types.ts | 7 ++++++ .../src/tools-panel/stories/index.story.tsx | 3 +++ .../src/utils/deprecated-36px-size.ts | 3 +++ 14 files changed, 51 insertions(+), 17 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index e5d467f22d30c8..ff4a8b714c9a52 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,7 @@ - `TextControl`: Deprecate 36px default size ([#66745](https://github.com/WordPress/gutenberg/pull/66745). - `FontSizePicker`: Deprecate 36px default size ([#66920](https://github.com/WordPress/gutenberg/pull/66920)). - `ComboboxControl`: Deprecate 36px default size ([#66900](https://github.com/WordPress/gutenberg/pull/66900)). +- `ToggleGroupControl`: Deprecate 36px default size ([#66747](https://github.com/WordPress/gutenberg/pull/66747)). ### Bug Fixes diff --git a/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx b/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx index 1e4cbcd1b72660..1b3619c800e453 100644 --- a/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx +++ b/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx @@ -19,6 +19,7 @@ const FontSizePickerToggleGroup = ( props: FontSizePickerToggleGroupProps ) => { = ( { return ( { setValue( ...changeArgs ); diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap index 832c6d7cb7a8c8..f344cd6ba16528 100644 --- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap @@ -44,8 +44,8 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { @@ -159,7 +159,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = width: 100%; z-index: 2; color: #1e1e1e; - height: 30px; + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; @@ -236,7 +236,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = width: 100%; z-index: 2; color: #1e1e1e; - height: 30px; + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; @@ -409,8 +409,8 @@ exports[`ToggleGroupControl controlled should render correctly with text options display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { @@ -678,8 +678,8 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { @@ -793,7 +793,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] width: 100%; z-index: 2; color: #1e1e1e; - height: 30px; + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; @@ -870,7 +870,7 @@ exports[`ToggleGroupControl uncontrolled should render correctly with icons 1`] width: 100%; z-index: 2; color: #1e1e1e; - height: 30px; + height: 32px; aspect-ratio: 1; padding-left: 0; padding-right: 0; @@ -1037,8 +1037,8 @@ exports[`ToggleGroupControl uncontrolled should render correctly with text optio display: inline-flex; min-width: 0; position: relative; - min-height: 36px; - padding: 2px; + min-height: 40px; + padding: 3px; } .emotion-8:hover { diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx index 168e8f498958b5..44cfda69c423cf 100644 --- a/packages/components/src/toggle-group-control/test/index.tsx +++ b/packages/components/src/toggle-group-control/test/index.tsx @@ -28,7 +28,13 @@ const hoverOutside = async () => { }; const ToggleGroupControl = ( props: ToggleGroupControlProps ) => { - return <_ToggleGroupControl { ...props } __nextHasNoMarginBottom />; + return ( + <_ToggleGroupControl + { ...props } + __nextHasNoMarginBottom + __next40pxDefaultSize + /> + ); }; const ControlledToggleGroupControl = ( { diff --git a/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md b/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md index a0e3a44cf74607..1ee82b26a91998 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md +++ b/packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md @@ -17,7 +17,7 @@ import { formatLowercase, formatUppercase } from '@wordpress/icons'; function Example() { return ( - + + * * * * diff --git a/packages/components/src/toggle-group-control/toggle-group-control/README.md b/packages/components/src/toggle-group-control/toggle-group-control/README.md index ca5c5d14eb6b5a..841d474c148d45 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/README.md +++ b/packages/components/src/toggle-group-control/toggle-group-control/README.md @@ -25,6 +25,7 @@ function Example() { value="vertical" isBlock __nextHasNoMarginBottom + __next40pxDefaultSize > @@ -100,4 +101,4 @@ Start opting into the larger default height that will become the default size in Start opting into the new margin-free styles that will become the default in a future version. - Required: No -- Default: `false` \ No newline at end of file +- Default: `false` diff --git a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx index 0c3cadf210d84a..9f3427e95a6017 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx @@ -23,6 +23,7 @@ import { ToggleGroupControlAsButtonGroup } from './as-button-group'; import { useTrackElementOffsetRect } from '../../utils/element-rect'; import { useMergeRefs } from '@wordpress/compose'; import { useAnimatedOffsetRect } from '../../utils/hooks/use-animated-offset-rect'; +import { maybeWarnDeprecated36pxSize } from '../../utils/deprecated-36px-size'; function UnconnectedToggleGroupControl( props: WordPressComponentProps< ToggleGroupControlProps, 'div', false >, @@ -31,6 +32,7 @@ function UnconnectedToggleGroupControl( const { __nextHasNoMarginBottom = false, __next40pxDefaultSize = false, + __shouldNotWarnDeprecated36pxSize, className, isAdaptiveWidth = false, isBlock = false, @@ -81,6 +83,13 @@ function UnconnectedToggleGroupControl( ? ToggleGroupControlAsButtonGroup : ToggleGroupControlAsRadioGroup; + maybeWarnDeprecated36pxSize( { + componentName: 'ToggleGroupControl', + size, + __next40pxDefaultSize, + __shouldNotWarnDeprecated36pxSize, + } ); + return ( * * diff --git a/packages/components/src/toggle-group-control/types.ts b/packages/components/src/toggle-group-control/types.ts index 463d8d26e64410..cfa9d00080467c 100644 --- a/packages/components/src/toggle-group-control/types.ts +++ b/packages/components/src/toggle-group-control/types.ts @@ -128,6 +128,13 @@ export type ToggleGroupControlProps = Pick< * @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; }; export type ToggleGroupControlContextProps = { diff --git a/packages/components/src/tools-panel/stories/index.story.tsx b/packages/components/src/tools-panel/stories/index.story.tsx index 459932c9d22d7a..787585ac716b51 100644 --- a/packages/components/src/tools-panel/stories/index.story.tsx +++ b/packages/components/src/tools-panel/stories/index.story.tsx @@ -110,6 +110,7 @@ export const Default: StoryFn< typeof ToolsPanel > = ( { > setScale( next ) } @@ -457,6 +458,7 @@ export const WithConditionalDefaultControl: StoryFn< typeof ToolsPanel > = ( { > @@ -559,6 +561,7 @@ export const WithConditionallyRenderedControl: StoryFn< > diff --git a/packages/components/src/utils/deprecated-36px-size.ts b/packages/components/src/utils/deprecated-36px-size.ts index be5baa2515637c..0f87c2cd270c73 100644 --- a/packages/components/src/utils/deprecated-36px-size.ts +++ b/packages/components/src/utils/deprecated-36px-size.ts @@ -7,12 +7,15 @@ export function maybeWarnDeprecated36pxSize( { componentName, __next40pxDefaultSize, size, + __shouldNotWarnDeprecated36pxSize, }: { componentName: string; __next40pxDefaultSize: boolean | undefined; size: string | undefined; + __shouldNotWarnDeprecated36pxSize?: boolean; } ) { if ( + __shouldNotWarnDeprecated36pxSize || __next40pxDefaultSize || ( size !== undefined && size !== 'default' ) ) { From 603e5c0e44cf6e231ffbc40d00c2bb851b49b9ef Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 20 Nov 2024 17:51:45 +0000 Subject: [PATCH 190/605] Update Changelog for 19.7.0 --- changelog.txt | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) diff --git a/changelog.txt b/changelog.txt index d9236e513469af..1a6b45a37fc7cf 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,296 @@ == Changelog == += 19.7.0 = + +## Changelog + +### Enhancements + +- Add "show template" to preview dropdown. ([66514](https://github.com/WordPress/gutenberg/pull/66514)) +- Iframe: Always enable for block themes, in core too. ([66800](https://github.com/WordPress/gutenberg/pull/66800)) +- Media Utils: Add experimental `sideloadMedia`. ([66378](https://github.com/WordPress/gutenberg/pull/66378)) +- Post fields: Clean up. ([66941](https://github.com/WordPress/gutenberg/pull/66941)) +- Post fields: Extract `title` from `edit-site` to `fields` package. ([66940](https://github.com/WordPress/gutenberg/pull/66940)) +- Post fields: Move `comment_status` from edit-site to fields package. ([66934](https://github.com/WordPress/gutenberg/pull/66934)) +- Post fields: Move `date` fields from `edit-site` to `fields` package. ([66938](https://github.com/WordPress/gutenberg/pull/66938)) +- Post fields: Move `status` from `edit-site` to `fields`. ([66937](https://github.com/WordPress/gutenberg/pull/66937)) +- Relocate “View” external link to end of editor header controls. ([66785](https://github.com/WordPress/gutenberg/pull/66785)) + +#### Block Library +- Added toggle control to set any image as feature image if no feature image is set for post. ([65896](https://github.com/WordPress/gutenberg/pull/65896)) +- Improve cover z-index solution. ([66249](https://github.com/WordPress/gutenberg/pull/66249)) +- Post Content: Add border and spacing support. ([66366](https://github.com/WordPress/gutenberg/pull/66366)) +- Query Loop: Use templateSlug and postType for more context. ([65820](https://github.com/WordPress/gutenberg/pull/65820)) +- Update text case of "Starter Content". ([66954](https://github.com/WordPress/gutenberg/pull/66954)) +- [Details Block]: Adds anchor support in details block. ([66734](https://github.com/WordPress/gutenberg/pull/66734)) + +#### Components +- Guide: Use small size button for page controls. ([66607](https://github.com/WordPress/gutenberg/pull/66607)) +- MenuItem: Add 40px size prop on Button. ([66596](https://github.com/WordPress/gutenberg/pull/66596)) +- Notice: Add appropriate size props to Buttons. ([66593](https://github.com/WordPress/gutenberg/pull/66593)) +- PaletteEdit: Add appropriate size props to Buttons. ([66590](https://github.com/WordPress/gutenberg/pull/66590)) +- Popover: Add small size prop to close button. ([66587](https://github.com/WordPress/gutenberg/pull/66587)) + +#### Global Styles +- Global styles revisions: Move focus and active state to list item. ([66780](https://github.com/WordPress/gutenberg/pull/66780)) +- Site editor: Integrate global styles controls and style book preview into the styles panel. ([65619](https://github.com/WordPress/gutenberg/pull/65619)) + +#### DataViews +- DataViews Fields API: Default getValueFromId supports nested objects. ([66890](https://github.com/WordPress/gutenberg/pull/66890)) + +#### Block Editor +- Inserter: Add 'Starter Content' category to the inserter. ([66819](https://github.com/WordPress/gutenberg/pull/66819)) + +#### Zoom Out +- Enable zoom out mode for non-iframe editor. ([66789](https://github.com/WordPress/gutenberg/pull/66789)) + +#### Themes +- Theme JSON Resolver: Remove theme json merge in resolve_theme_file_uris. ([66662](https://github.com/WordPress/gutenberg/pull/66662)) + +#### Edit Mode +- Image block: Add support for "more" dropdown for additional tools in Write mode. ([66605](https://github.com/WordPress/gutenberg/pull/66605)) + +#### Style Book +- Add a landing section to stylebook tabs. ([66545](https://github.com/WordPress/gutenberg/pull/66545)) + +#### Media +- Media Library: Expose filters dropdown for individual images, such as with the Image block. ([65965](https://github.com/WordPress/gutenberg/pull/65965)) + + +### Bug Fixes + +- Block toolbar: Restrict visible child calculation to known blocks. ([66702](https://github.com/WordPress/gutenberg/pull/66702)) +- ComplementaryArea: Fix button position. ([66677](https://github.com/WordPress/gutenberg/pull/66677)) +- Fix Paragraph appender layout shift (building on 66061). ([66779](https://github.com/WordPress/gutenberg/pull/66779)) +- Fix: Set the `fit-content` width for images that are not `.svg`. ([66643](https://github.com/WordPress/gutenberg/pull/66643)) +- Preference modal: Avoid fetching all reusable blocks when the site editor loads. ([66621](https://github.com/WordPress/gutenberg/pull/66621)) +- Revert "Set image width to `fit-content` to solve aspect ratio problems in Firefox. (#66217)". ([66804](https://github.com/WordPress/gutenberg/pull/66804)) +- Safari: Fix site editor template error. ([66647](https://github.com/WordPress/gutenberg/pull/66647)) +- Safari: Prevent focus capturing caused by flex display. ([66402](https://github.com/WordPress/gutenberg/pull/66402)) +- Select Mode: Hide tool selector in the post editor and force design mode. ([66784](https://github.com/WordPress/gutenberg/pull/66784)) +- Shadow panel: Make the delete modal text translatable. ([66712](https://github.com/WordPress/gutenberg/pull/66712)) +- Site Editor: Fix template for page-on-front option. ([66739](https://github.com/WordPress/gutenberg/pull/66739)) +- WP Scripts: Make watch mode more resilient for developer errors. ([66752](https://github.com/WordPress/gutenberg/pull/66752)) +- getDefaultTemplateId: Ensure entity configuration is loaded. ([66650](https://github.com/WordPress/gutenberg/pull/66650)) +- Comments controller: fix issue where comments are allowed when closed (https://github.com/WordPress/gutenberg/pull/66976) + +#### Block Library +- Cover: Fix media library image selection. ([66782](https://github.com/WordPress/gutenberg/pull/66782)) +- Cover: Show DropZone only when dragging withing the block. ([66912](https://github.com/WordPress/gutenberg/pull/66912)) +- Media & Text: Set `.wp-block-media-text__media a` display to block. ([66915](https://github.com/WordPress/gutenberg/pull/66915)) +- Prevent duplicate post format taxonomy queries. ([66627](https://github.com/WordPress/gutenberg/pull/66627)) +- Query Loop: Check for postTypeFromContext before using it. ([66655](https://github.com/WordPress/gutenberg/pull/66655)) +- Query Loop: Remove postTypeFromContext. ([66681](https://github.com/WordPress/gutenberg/pull/66681)) + +#### Block Editor +- Appender: Fix initial position. ([66711](https://github.com/WordPress/gutenberg/pull/66711)) +- Appender: Fix outside canvas styles. ([66630](https://github.com/WordPress/gutenberg/pull/66630)) +- Block Inspector: Restore bottom margin for RadioControl. ([66688](https://github.com/WordPress/gutenberg/pull/66688)) +- Iframed editor: Fix relative wp-content URLs. ([66751](https://github.com/WordPress/gutenberg/pull/66751)) + +#### Global Styles +- Section Styles: Fix insecure properties removal for inner block types and elements. ([66896](https://github.com/WordPress/gutenberg/pull/66896)) +- Style book: Reduce margin selector specificity so that it doesn't override global block styles. ([66895](https://github.com/WordPress/gutenberg/pull/66895)) +- Theme JSON: Replace top-level background style objects on merge. ([66656](https://github.com/WordPress/gutenberg/pull/66656)) + +#### Components +- FormTokenField: Fix token styles. ([66640](https://github.com/WordPress/gutenberg/pull/66640)) +- Storybook: Fix DataViews action modals. ([66727](https://github.com/WordPress/gutenberg/pull/66727)) +- ToggleGroupControl: Fix active background for `zero` value. ([66855](https://github.com/WordPress/gutenberg/pull/66855)) + +#### Post Editor +- Disable device preview button in pattern/template part/navitation editor. ([65970](https://github.com/WordPress/gutenberg/pull/65970)) +- PostTaxonomiesFlatTermSelector: Abstract wrapper component. ([66625](https://github.com/WordPress/gutenberg/pull/66625)) +- VisualEditor: Always output has-global-padding classname when in post only mode. ([66626](https://github.com/WordPress/gutenberg/pull/66626)) + +#### DataViews +- Fix TypeError when duplicating uncategorized theme patterns. ([66889](https://github.com/WordPress/gutenberg/pull/66889)) +- Tweak primary field in patterns grid layout. ([66733](https://github.com/WordPress/gutenberg/pull/66733)) + +#### Meta Boxes +- Fix: Show Meta Boxes at the bottom of the screen regardless of the current rendering mode. ([66508](https://github.com/WordPress/gutenberg/pull/66508)) +- Hide metaboxes in Zoom Out. ([66886](https://github.com/WordPress/gutenberg/pull/66886)) + +#### Site Editor +- DataViews: Fix 'aria-label' for pattern preview element. ([66601](https://github.com/WordPress/gutenberg/pull/66601)) +- Site Hub: Fixed navigation redirect on mobile devices for classic themes. ([66867](https://github.com/WordPress/gutenberg/pull/66867)) + +#### Media +- Add `x-wav` mime type for wav files in Firefox. ([66850](https://github.com/WordPress/gutenberg/pull/66850)) +- Ensure HEIC files selectable from “Upload” button. ([66292](https://github.com/WordPress/gutenberg/pull/66292)) + +#### Patterns +- Fix uncategorized pattern browsing when pattern has no categories. ([66945](https://github.com/WordPress/gutenberg/pull/66945)) + +#### Interactivity API +- Fix property modification from inherited context two or more levels above. ([66872](https://github.com/WordPress/gutenberg/pull/66872)) + +#### Block API +- Process Block Type: Copy deprecation to a new object instead of mutating when stabilizing supports. ([66849](https://github.com/WordPress/gutenberg/pull/66849)) + +#### Design Tools +- Block Gap: Fix block spacing control for axial gap supported blocks. ([66783](https://github.com/WordPress/gutenberg/pull/66783)) + +#### Document Settings +- Editor: Restore the 'PluginPostStatusInfo' slot position. ([66665](https://github.com/WordPress/gutenberg/pull/66665)) + +#### Templates API +- Fix flash when clicking template name in the editor when a plugin registered template matches a default WP theme template. ([66359](https://github.com/WordPress/gutenberg/pull/66359)) + +#### Block bindings +- Fix unset array key warning in block-bindings.php. ([66337](https://github.com/WordPress/gutenberg/pull/66337)) + + +### Accessibility + +- Fix : Snackbar Notice Inconsistency. ([66405](https://github.com/WordPress/gutenberg/pull/66405)) +- Image: Add `aria-haspopup` prop write mode `more` tools menu items. ([66815](https://github.com/WordPress/gutenberg/pull/66815)) +- Site Icon Focus fix. ([66952](https://github.com/WordPress/gutenberg/pull/66952)) + +#### Components +- Popover: Fix missing label of the headerTitle Close button. ([66813](https://github.com/WordPress/gutenberg/pull/66813)) + +#### Post Editor +- Fix inconsistent sidebars close buttons sizes. ([66756](https://github.com/WordPress/gutenberg/pull/66756)) + +#### Block Library +- Remove unnecessary tooltip from Video block Text tracks button. ([66716](https://github.com/WordPress/gutenberg/pull/66716)) + +#### Block Editor +- Speak 'Block moved up/down' after using keyboard actions to move up/down. ([64966](https://github.com/WordPress/gutenberg/pull/64966)) + +#### Patterns +- Block Patterns List: Fix visual title and tooltip inconsistencies. ([64815](https://github.com/WordPress/gutenberg/pull/64815)) + + +### Performance + +- Inline Commenting: Avoid querying comments on editor load. ([66670](https://github.com/WordPress/gutenberg/pull/66670)) +- Patterns: Receive intermediate responses while unbound request is resolving. ([66713](https://github.com/WordPress/gutenberg/pull/66713)) +- Perf metrics: Update select and other metrics to use non-empty paragraphs. ([66762](https://github.com/WordPress/gutenberg/pull/66762)) +- Site Editor: Preload settings requests. ([66488](https://github.com/WordPress/gutenberg/pull/66488)) +- Site Editor: Speed up load by preloading home and front-page templates. ([66579](https://github.com/WordPress/gutenberg/pull/66579)) +- Site editor: Preload post if needed. ([66631](https://github.com/WordPress/gutenberg/pull/66631)) + +#### Global Styles +- Preload user global styles based on user caps. ([66541](https://github.com/WordPress/gutenberg/pull/66541)) + + +### Experiments + +- Add `isVisible` option to fields within DataForm. ([65826](https://github.com/WordPress/gutenberg/pull/65826)) +- DataViews: Implement `isItemClickable` and `onClickItem` props. ([66365](https://github.com/WordPress/gutenberg/pull/66365)) + +#### DataViews +- Quick Edit - Slug Field: Improve slug preview. ([66559](https://github.com/WordPress/gutenberg/pull/66559)) +- QuickEdit: Add password field data to the pages quick edit. ([66567](https://github.com/WordPress/gutenberg/pull/66567)) + + +### Documentation + +- Add 6.6.2 to Version in WordPress. ([66870](https://github.com/WordPress/gutenberg/pull/66870)) +- Add missing properties for DataViews/DataForm components. ([66749](https://github.com/WordPress/gutenberg/pull/66749)) +- Add section about the Fields API. ([66761](https://github.com/WordPress/gutenberg/pull/66761)) +- Block Bindings: Documentation API reference. ([66251](https://github.com/WordPress/gutenberg/pull/66251)) +- Docs: Include a note about supported licenses in WordPress packages. ([66562](https://github.com/WordPress/gutenberg/pull/66562)) +- Document `filterSortAndPaginate` & `isItemValid` utilities. ([66738](https://github.com/WordPress/gutenberg/pull/66738)) +- Feat: Storybook: Improve component organisation - Navigation Category - Issue #66275. ([66658](https://github.com/WordPress/gutenberg/pull/66658)) +- Feat: Storybook: Improve component organisation - Overlays Category - Issue #66275. ([66657](https://github.com/WordPress/gutenberg/pull/66657)) +- Feat: Storybook: Improve component organisation - Selection & Input Category - Issue #66275. ([66660](https://github.com/WordPress/gutenberg/pull/66660)) +- Feat: Storybook: Improve component organisation - Typography - Issue #66275. ([66633](https://github.com/WordPress/gutenberg/pull/66633)) +- Improve readability of DataViews documentation. ([66766](https://github.com/WordPress/gutenberg/pull/66766)) +- Move documentation for filter operators to proper place. ([66743](https://github.com/WordPress/gutenberg/pull/66743)) +- Reorganize to bootstrap DataForm API section. ([66729](https://github.com/WordPress/gutenberg/pull/66729)) +- Storybook: Improve component organisation - Actions. ([66680](https://github.com/WordPress/gutenberg/pull/66680)) +- Storybook: Log `warning()` when in dev mode. ([66568](https://github.com/WordPress/gutenberg/pull/66568)) +- Update Commands documentation with the existing contexts. ([66860](https://github.com/WordPress/gutenberg/pull/66860)) + + +### Code Quality + +- BlockPatternsList: Use the Async component. ([66744](https://github.com/WordPress/gutenberg/pull/66744)) +- Core Commands: Fix add new post URL assignment. ([66830](https://github.com/WordPress/gutenberg/pull/66830)) +- Inline Commenting: Optimize store selector and misc changes. ([66592](https://github.com/WordPress/gutenberg/pull/66592)) +- Remove unnecessary boolean assignments. ([66857](https://github.com/WordPress/gutenberg/pull/66857)) +- TypeScript: Fix and improve types for private-apis. ([66667](https://github.com/WordPress/gutenberg/pull/66667)) + +#### Block Editor +- Fix 'useSelect' dependencies for the 'RichText' component. ([66964](https://github.com/WordPress/gutenberg/pull/66964)) +- Fix ESLint warning for 'useBlockTypesState' hook. ([66757](https://github.com/WordPress/gutenberg/pull/66757)) +- Fix React Compiler error for 'BlockProps' util component. ([66809](https://github.com/WordPress/gutenberg/pull/66809)) +- Optimize `getVisibleElementBounds` in scrollable cases. ([66546](https://github.com/WordPress/gutenberg/pull/66546)) +- Revert: Fix unable to remove empty blocks on merge (#65262) + alternative. ([66564](https://github.com/WordPress/gutenberg/pull/66564)) +- URLInput: Fix incorrect classname for suggestions. ([66714](https://github.com/WordPress/gutenberg/pull/66714)) + +#### Site Editor +- Avoid using edited entity state in site editor loading hook. ([66924](https://github.com/WordPress/gutenberg/pull/66924)) +- Avoid using edited post selectors in welcome guide. ([66926](https://github.com/WordPress/gutenberg/pull/66926)) +- Edit Site: Refactor to remove usage of edited entity state. ([66922](https://github.com/WordPress/gutenberg/pull/66922)) +- Edit Site: Remove leftover 'priority-queue' dependency. ([66773](https://github.com/WordPress/gutenberg/pull/66773)) +- Remove useEditedEntityRecord hook. ([66955](https://github.com/WordPress/gutenberg/pull/66955)) + +#### Components +- Fix React Compiler error for 'useScrollRectIntoView'. ([66498](https://github.com/WordPress/gutenberg/pull/66498)) +- Panel: Add 40px size prop to Button. ([66589](https://github.com/WordPress/gutenberg/pull/66589)) +- Radio: Deprecate 36px default size. ([66572](https://github.com/WordPress/gutenberg/pull/66572)) +- Snackbar: Use `link` variant for action Button. ([66560](https://github.com/WordPress/gutenberg/pull/66560)) + +#### Data Layer +- Convert the emitter module in data package to TS. ([66669](https://github.com/WordPress/gutenberg/pull/66669)) +- Data: Rename useSelect internals to fix React Compiler violations. ([66807](https://github.com/WordPress/gutenberg/pull/66807)) +- Data: Upgrade Redux to v5.0.1. ([66966](https://github.com/WordPress/gutenberg/pull/66966)) + +#### Post Editor +- ESLint: Fix React Compiler violations in various commands. ([66787](https://github.com/WordPress/gutenberg/pull/66787)) +- Fix TS types for editor package. ([66754](https://github.com/WordPress/gutenberg/pull/66754)) + +#### Zoom Out +- Zoom-out: Move default background to the iframe component. ([66284](https://github.com/WordPress/gutenberg/pull/66284)) + +#### Design Tools +- Typography: Stabilize typography block supports within block processing. ([63401](https://github.com/WordPress/gutenberg/pull/63401)) + + +### Tools + +#### Testing +- Media: Check for `wav` mime type using isset. ([66947](https://github.com/WordPress/gutenberg/pull/66947)) + +#### Build Tooling +- Enforce the same order of fields in `package.json` files. ([66239](https://github.com/WordPress/gutenberg/pull/66239)) +- Introduce React Scanner for component usage stats. ([65463](https://github.com/WordPress/gutenberg/pull/65463)) + + +### Various + +- Style engine: Wrap array_merge in conditionals to prevent unnecessary merging. ([66661](https://github.com/WordPress/gutenberg/pull/66661)) + +#### Block Library +- Update placeholder text for blocks that support drag and drop. ([66842](https://github.com/WordPress/gutenberg/pull/66842)) +- update: Add Media to Add media in cover block. ([66835](https://github.com/WordPress/gutenberg/pull/66835)) + + +## First-time contributors + +The following PRs were merged by first-time contributors: + +- @benharri: Fix unset array key warning in block-bindings.php. ([66337](https://github.com/WordPress/gutenberg/pull/66337)) +- @benniledl: Add 6.6.2 to Version in WordPress. ([66870](https://github.com/WordPress/gutenberg/pull/66870)) +- @Infinite-Null: Media & Text: Set `.wp-block-media-text__media a` display to block. ([66915](https://github.com/WordPress/gutenberg/pull/66915)) +- @karthick-murugan: Site Icon Focus fix. ([66952](https://github.com/WordPress/gutenberg/pull/66952)) +- @rinkalpagdar: Post Content: Add border and spacing support. ([66366](https://github.com/WordPress/gutenberg/pull/66366)) +- @yogeshbhutkar: Site Hub: Fixed navigation redirect on mobile devices for classic themes. ([66867](https://github.com/WordPress/gutenberg/pull/66867)) + + +## Contributors + +The following contributors merged PRs in this release: + +@aaronrobertshaw @adamsilverstein @afercia @Aljullu @amitraj2203 @andrewserong @benharri @benniledl @carolinan @cbravobernal @DAreRodz @dcalhoun @ellatrix @fabiankaegy @gigitux @gziolo @hbhalodia @Infinite-Null @jasmussen @jorgefilipecosta @jsnajdr @juanfra @karthick-murugan @kevin940726 @louwie17 @Mamaduka @manzoorwanijk @matiasbenedetto @mikachan @mirka @n2erjo00 @ntsekouras @oandregal @ramonjd @renatho @rinkalpagdar @Soean @stokesman @swissspidy @t-hamano @tellthemachines @tyxla @up1512001 @Vrishabhsk @yogeshbhutkar @youknowriad + + + + = 19.6.4 = - PostTaxonomiesFlatTermSelector: abstract wrapper component (#66625) From d57502ee6bad7bd6bf352a73700ba2e61d79c3ef Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:57:53 +0100 Subject: [PATCH 191/605] Navigation Block: Remove obsolete Block Hooks filters (#64676) --- .../block-library/src/navigation/index.php | 150 ------------------ .../block-navigation-block-hooks-test.php | 148 ----------------- 2 files changed, 298 deletions(-) delete mode 100644 phpunit/blocks/block-navigation-block-hooks-test.php diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index dd300eb12c6feb..ae3b9620a33584 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -1520,153 +1520,3 @@ function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) { return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor ); } - -/** - * Insert ignoredHookedBlocks meta into the Navigation block and its inner blocks. - * - * Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object, - * this function inserts ignoredHookedBlocks meta into it, and returns the serialized inner blocks in a - * mock Navigation block wrapper. - * - * @since 6.5.0 - * - * @param array $inner_blocks Parsed inner blocks of a Navigation block. - * @param WP_Post $post `wp_navigation` post object corresponding to the block. - * @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any. - */ -function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks, $post ) { - $mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post ); - $hooked_blocks = get_hooked_blocks(); - $before_block_visitor = null; - $after_block_visitor = null; - - if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { - $before_block_visitor = make_before_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' ); - $after_block_visitor = make_after_block_visitor( $hooked_blocks, $post, 'set_ignored_hooked_blocks_metadata' ); - } - - return traverse_and_serialize_block( $mock_navigation_block, $before_block_visitor, $after_block_visitor ); -} - -/** - * Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API. - * - * @access private - * @since 6.5.0 - * - * @param stdClass $post Post object. - * @return stdClass The updated post object. - */ -function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) { - /* - * In this scenario the user has likely tried to create a navigation via the REST API. - * In which case we won't have a post ID to work with and store meta against. - */ - if ( empty( $post->ID ) ) { - return $post; - } - - /** - * Skip meta generation when consumers intentionally update specific Navigation fields - * and omit the content update. - */ - if ( ! isset( $post->post_content ) ) { - return $post; - } - - /* - * We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into - * all anchor blocks. For the root level, we create a mock Navigation and extract them from there. - */ - $blocks = parse_blocks( $post->post_content ); - - /* - * Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that - * we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be - * used as context for hooked blocks insertion). - * We thus have to look it up from the DB,based on `$post->ID`. - */ - $markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) ); - - $root_nav_block = parse_blocks( $markup )[0]; - $ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] ) - ? $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] - : array(); - - if ( ! empty( $ignored_hooked_blocks ) ) { - $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); - if ( ! empty( $existing_ignored_hooked_blocks ) ) { - $existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true ); - $ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) ); - } - update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) ); - } - - $post->post_content = block_core_navigation_remove_serialized_parent_block( $markup ); - return $post; -} - -/* - * Before adding our filter, we verify if it's already added in Core. - * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_". - * Therefore, we concatenate the Core's function name to circumvent this prefix for our check. - */ -$rest_insert_wp_navigation_core_callback = 'block_core_navigation_' . 'update_ignore_hooked_blocks_meta'; // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found - -/* - * Do not add the `block_core_navigation_update_ignore_hooked_blocks_meta` filter in the following cases: - * - If Core has added the `update_ignored_hooked_blocks_postmeta` filter already (WP >= 6.6); - * - or if the `$rest_insert_wp_navigation_core_callback` filter has already been added. - */ -if ( - ! has_filter( 'rest_pre_insert_wp_navigation', 'update_ignored_hooked_blocks_postmeta' ) && - ! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) -) { - add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta' ); -} - -/** - * Hooks into the REST API response for the core/navigation block and adds the first and last inner blocks. - * - * @since 6.5.0 - * - * @param WP_REST_Response $response The response object. - * @param WP_Post $post Post object. - * @return WP_REST_Response The response object. - */ -function block_core_navigation_insert_hooked_blocks_into_rest_response( $response, $post ) { - if ( ! isset( $response->data['content']['raw'] ) || ! isset( $response->data['content']['rendered'] ) ) { - return $response; - } - $parsed_blocks = parse_blocks( $response->data['content']['raw'] ); - $content = block_core_navigation_insert_hooked_blocks( $parsed_blocks, $post ); - - // Remove mock Navigation block wrapper. - $content = block_core_navigation_remove_serialized_parent_block( $content ); - - $response->data['content']['raw'] = $content; - - /** This filter is documented in wp-includes/post-template.php */ - $response->data['content']['rendered'] = apply_filters( 'the_content', $content ); - - return $response; -} - -/* - * Before adding our filter, we verify if it's already added in Core. - * However, during the build process, Gutenberg automatically prefixes our functions with "gutenberg_". - * Therefore, we concatenate the Core's function name to circumvent this prefix for our check. - */ -$rest_prepare_wp_navigation_core_callback = 'block_core_navigation_' . 'insert_hooked_blocks_into_rest_response'; - -/* - * Do not add the `block_core_navigation_insert_hooked_blocks_into_rest_response` filter in the following cases: - * - If Core has added the `insert_hooked_blocks_into_rest_response` filter already (WP >= 6.6); - * - or if the `$rest_prepare_wp_navigation_core_callback` filter has already been added. - */ -if ( - ! has_filter( 'rest_prepare_wp_navigation', 'insert_hooked_blocks_into_rest_response' ) && - ! has_filter( 'rest_prepare_wp_navigation', $rest_prepare_wp_navigation_core_callback ) -) { - add_filter( 'rest_prepare_wp_navigation', 'block_core_navigation_insert_hooked_blocks_into_rest_response', 10, 3 ); -} diff --git a/phpunit/blocks/block-navigation-block-hooks-test.php b/phpunit/blocks/block-navigation-block-hooks-test.php deleted file mode 100644 index 1d3c86bf4bca4b..00000000000000 --- a/phpunit/blocks/block-navigation-block-hooks-test.php +++ /dev/null @@ -1,148 +0,0 @@ -'; - - self::$navigation_post = self::factory()->post->create_and_get( - array( - 'post_type' => 'wp_navigation', - 'post_title' => 'Navigation Menu', - 'post_content' => 'Original content', - ) - ); - } - - /** - * Tear down each test method. - */ - public function tear_down() { - $registry = WP_Block_Type_Registry::get_instance(); - - if ( $registry->is_registered( 'tests/my-block' ) ) { - $registry->unregister( 'tests/my-block' ); - } - - parent::tear_down(); - } - - /** - * @covers ::gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta - */ - public function test_block_core_navigation_update_ignore_hooked_blocks_meta_preserves_entities() { - register_block_type( - 'tests/my-block', - array( - 'block_hooks' => array( - 'core/navigation' => 'last_child', - ), - ) - ); - - $original_markup = ''; - $post = new stdClass(); - $post->ID = self::$navigation_post->ID; - $post->post_content = $original_markup; - - $post = gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta( $post ); - - // We expect the '&' character to be replaced with its unicode representation. - $expected_markup = str_replace( '&', '\u0026', $original_markup ); - - $this->assertSame( - $expected_markup, - $post->post_content, - 'Post content did not match expected markup with entities escaped.' - ); - $this->assertSame( - array( 'tests/my-block' ), - json_decode( get_post_meta( self::$navigation_post->ID, '_wp_ignored_hooked_blocks', true ), true ), - 'Block was not added to ignored hooked blocks metadata.' - ); - } - - /** - * @covers ::gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta - */ - public function test_block_core_navigation_dont_modify_no_post_id() { - register_block_type( - 'tests/my-block', - array( - 'block_hooks' => array( - 'core/navigation' => 'last_child', - ), - ) - ); - - $original_markup = ''; - $post = new stdClass(); - $post->post_content = $original_markup; - - $post = gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta( $post ); - - $this->assertSame( - $original_markup, - $post->post_content, - 'Post content did not match the original markup.' - ); - } - - /** - * @covers ::gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta - */ - public function test_block_core_navigation_retains_content_if_not_set() { - register_block_type( - 'tests/my-block', - array( - 'block_hooks' => array( - 'core/navigation' => 'last_child', - ), - ) - ); - - $post = new stdClass(); - $post->ID = self::$navigation_post->ID; - $post->post_title = 'Navigation Menu with changes'; - - $post = gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta( $post ); - - $this->assertSame( - 'Navigation Menu with changes', - $post->post_title, - 'Post title was changed.' - ); - - $this->assertFalse( - isset( $post->post_content ), - 'Post content should not be set.' - ); - } -} From b7d989e0dafb23866f65295a851e6b8734c3b693 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Thu, 21 Nov 2024 13:58:58 +0800 Subject: [PATCH 192/605] Fix typo in use-block-sync tests (#67145) Co-authored-by: talldan Co-authored-by: ramonjd --- .../src/components/provider/test/use-block-sync.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/provider/test/use-block-sync.js b/packages/block-editor/src/components/provider/test/use-block-sync.js index aae5e517c63029..b2afdb942e66fa 100644 --- a/packages/block-editor/src/components/provider/test/use-block-sync.js +++ b/packages/block-editor/src/components/provider/test/use-block-sync.js @@ -22,7 +22,9 @@ jest.mock( '../../../store/actions', () => { ...actions, resetBlocks: jest.fn( actions.resetBlocks ), replaceInnerBlocks: jest.fn( actions.replaceInnerBlocks ), - setHasControlledInnerBlocks: jest.fn( actions.replaceInnerBlocks ), + setHasControlledInnerBlocks: jest.fn( + actions.setHasControlledInnerBlocks + ), }; } ); From afb05e8b9c2dbdc43c119f2c756f57620a4febfc Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <150562+mcsf@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:41:53 +0000 Subject: [PATCH 193/605] useBlockNameForPatterns: Refactor as a single useSelect call (#67171) --- packages/block-library/src/query/utils.js | 29 +++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js index fc22ca46d471c0..e12fdc8d8a7e89 100644 --- a/packages/block-library/src/query/utils.js +++ b/packages/block-library/src/query/utils.js @@ -272,32 +272,31 @@ export const getTransformedBlocksFromPattern = ( * @return {string} The block name to be used in the patterns suggestions. */ export function useBlockNameForPatterns( clientId, attributes ) { - const activeVariationName = useSelect( - ( select ) => - select( blocksStore ).getActiveBlockVariation( - 'core/query', - attributes - )?.name, - [ attributes ] - ); - const blockName = `core/query/${ activeVariationName }`; - const hasActiveVariationPatterns = useSelect( + return useSelect( ( select ) => { + const activeVariationName = select( + blocksStore + ).getActiveBlockVariation( 'core/query', attributes )?.name; + if ( ! activeVariationName ) { - return false; + return 'core/query'; } + const { getBlockRootClientId, getPatternsByBlockTypes } = select( blockEditorStore ); + const rootClientId = getBlockRootClientId( clientId ); const activePatterns = getPatternsByBlockTypes( - blockName, + `core/query/${ activeVariationName }`, rootClientId ); - return activePatterns.length > 0; + + return activePatterns.length > 0 + ? `core/query/${ activeVariationName }` + : 'core/query'; }, - [ clientId, activeVariationName, blockName ] + [ clientId, attributes ] ); - return hasActiveVariationPatterns ? blockName : 'core/query'; } /** From b5732a58e930f28cb8aa34d2d1d2ca741e2abd93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:15:26 +0100 Subject: [PATCH 194/605] DataViews: fix action visibility logic (#67197) Co-authored-by: oandregal Co-authored-by: ntsekouras --- .../dataviews/src/components/dataviews-item-actions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dataviews/src/components/dataviews-item-actions/index.tsx b/packages/dataviews/src/components/dataviews-item-actions/index.tsx index 47e65bc81cb171..787cef4420acc0 100644 --- a/packages/dataviews/src/components/dataviews-item-actions/index.tsx +++ b/packages/dataviews/src/components/dataviews-item-actions/index.tsx @@ -190,7 +190,7 @@ function hasOnlyOneActionAndIsPrimary< Item >( primaryActions: Action< Item >[], actions: Action< Item >[] ) { - return primaryActions.length === 1 && actions.length; + return primaryActions.length === 1 && actions.length === 1; } export default function ItemActions< Item >( { From ec4b881cf89a426772b10f265b400f701751ffd5 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Thu, 21 Nov 2024 15:10:24 +0200 Subject: [PATCH 195/605] Place "Write mode" functionality behind a Gutenberg experiment (#67008) Co-authored-by: ntsekouras Co-authored-by: mcsf Co-authored-by: carolinan Co-authored-by: youknowriad Co-authored-by: richtabor Co-authored-by: annezazu --- lib/experimental/editor-settings.php | 3 + lib/experiments-page.php | 12 ++ packages/block-editor/src/store/selectors.js | 3 + .../block-editor/src/store/test/selectors.js | 145 +++++++++++------- .../src/components/document-tools/index.js | 5 +- .../editor/various/write-design-mode.spec.js | 8 +- test/e2e/specs/site-editor/style-book.spec.js | 3 + 7 files changed, 115 insertions(+), 64 deletions(-) diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index afc6d7e220f676..5b36c32b3c8296 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -37,6 +37,9 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-media-processing', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalMediaProcessing = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-editor-write-mode', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEditorWriteMode = true', 'before' ); + } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 946b68283a3e0b..9033e3c2d0c1fb 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -187,6 +187,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-editor-write-mode', + __( 'Editor write mode', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable write mode in editor.', 'gutenberg' ), + 'id' => 'gutenberg-editor-write-mode', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 598b6b4ea480de..ac1d178f43de7c 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -2774,6 +2774,9 @@ export function isNavigationMode( state ) { */ export const __unstableGetEditorMode = createRegistrySelector( ( select ) => ( state ) => { + if ( ! window?.__experimentalEditorWriteMode ) { + return 'edit'; + } return ( state.settings.editorTool ?? select( preferencesStore ).get( 'core', 'editorTool' ) diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 00aa085f667093..7c0361449c5fca 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -4615,68 +4615,97 @@ describe( 'getBlockEditingMode', () => { ).toBe( 'contentOnly' ); } ); - it( 'in navigation mode, the root section container is default', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' - ) - ).toBe( 'default' ); - } ); + describe( 'navigation mode', () => { + const writeModeExperiment = window.__experimentalEditorWriteMode; + beforeAll( () => { + window.__experimentalEditorWriteMode = true; + } ); + afterAll( () => { + window.__experimentalEditorWriteMode = writeModeExperiment; + } ); + it( 'in navigation mode, the root section container is default', () => { + dispatch( preferencesStore ).set( + 'core', + 'editorTool', + 'navigation' + ); + expect( + getBlockEditingMode( + navigationModeStateWithRootSection, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( 'default' ); + } ); - it( 'in navigation mode, anything outside the section container is disabled', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - '6cf70164-9097-4460-bcbf-200560546988' - ) - ).toBe( 'disabled' ); - } ); + it( 'in navigation mode, anything outside the section container is disabled', () => { + dispatch( preferencesStore ).set( + 'core', + 'editorTool', + 'navigation' + ); + expect( + getBlockEditingMode( + navigationModeStateWithRootSection, + '6cf70164-9097-4460-bcbf-200560546988' + ) + ).toBe( 'disabled' ); + } ); - it( 'in navigation mode, sections are contentOnly', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'b26fc763-417d-4f01-b81c-2ec61e14a972' - ) - ).toBe( 'contentOnly' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f' - ) - ).toBe( 'contentOnly' ); - } ); + it( 'in navigation mode, sections are contentOnly', () => { + dispatch( preferencesStore ).set( + 'core', + 'editorTool', + 'navigation' + ); + expect( + getBlockEditingMode( + navigationModeStateWithRootSection, + 'b26fc763-417d-4f01-b81c-2ec61e14a972' + ) + ).toBe( 'contentOnly' ); + expect( + getBlockEditingMode( + navigationModeStateWithRootSection, + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f' + ) + ).toBe( 'contentOnly' ); + } ); - it( 'in navigation mode, blocks with content attributes within sections are contentOnly', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - hasContentRoleAttribute.mockReturnValueOnce( true ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'contentOnly' ); + it( 'in navigation mode, blocks with content attributes within sections are contentOnly', () => { + dispatch( preferencesStore ).set( + 'core', + 'editorTool', + 'navigation' + ); + hasContentRoleAttribute.mockReturnValueOnce( true ); + expect( + getBlockEditingMode( + navigationModeStateWithRootSection, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'contentOnly' ); - hasContentRoleAttribute.mockReturnValueOnce( true ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' - ) - ).toBe( 'contentOnly' ); - } ); + hasContentRoleAttribute.mockReturnValueOnce( true ); + expect( + getBlockEditingMode( + navigationModeStateWithRootSection, + 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' + ) + ).toBe( 'contentOnly' ); + } ); - it( 'in navigation mode, blocks without content attributes within sections are disabled', () => { - dispatch( preferencesStore ).set( 'core', 'editorTool', 'navigation' ); - expect( - getBlockEditingMode( - navigationModeStateWithRootSection, - '9b9c5c3f-2e46-4f02-9e14-9fed515b958s' - ) - ).toBe( 'disabled' ); + it( 'in navigation mode, blocks without content attributes within sections are disabled', () => { + dispatch( preferencesStore ).set( + 'core', + 'editorTool', + 'navigation' + ); + expect( + getBlockEditingMode( + navigationModeStateWithRootSection, + '9b9c5c3f-2e46-4f02-9e14-9fed515b958s' + ) + ).toBe( 'disabled' ); + } ); } ); } ); diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js index 74118caaf5849c..a98def685e93a6 100644 --- a/packages/editor/src/components/document-tools/index.js +++ b/packages/editor/src/components/document-tools/index.js @@ -60,8 +60,9 @@ function DocumentTools( { className, disableBlockTools = false } ) { isDistractionFree: get( 'core', 'distractionFree' ), isVisualMode: getEditorMode() === 'visual', showTools: - getRenderingMode() !== 'post-only' || - getCurrentPostType() === 'wp_template', + !! window?.__experimentalEditorWriteMode && + ( getRenderingMode() !== 'post-only' || + getCurrentPostType() === 'wp_template' ), }; }, [] ); 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 2116f9042685af..053f4cb8ff092a 100644 --- a/test/e2e/specs/editor/various/write-design-mode.spec.js +++ b/test/e2e/specs/editor/various/write-design-mode.spec.js @@ -7,19 +7,19 @@ test.describe( 'Write/Design mode', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'emptytheme' ); } ); - - test.beforeEach( async ( { admin } ) => { + test.beforeEach( async ( { admin, page } ) => { + await page.addInitScript( () => { + window.__experimentalEditorWriteMode = true; + } ); await admin.visitSiteEditor( { postId: 'emptytheme//index', postType: 'wp_template', canvas: 'edit', } ); } ); - test.afterAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'twentytwentyone' ); } ); - test( 'Should prevent selecting intermediary blocks', async ( { editor, page, diff --git a/test/e2e/specs/site-editor/style-book.spec.js b/test/e2e/specs/site-editor/style-book.spec.js index d860b05bc8f06d..38030892826ec7 100644 --- a/test/e2e/specs/site-editor/style-book.spec.js +++ b/test/e2e/specs/site-editor/style-book.spec.js @@ -19,6 +19,9 @@ test.describe( 'Style Book', () => { } ); test.beforeEach( async ( { admin, editor, styleBook, page } ) => { + await page.addInitScript( () => { + window.__experimentalEditorWriteMode = true; + } ); await admin.visitSiteEditor(); await editor.canvas.locator( 'body' ).click(); await styleBook.open(); From 0c32e9e88c7e2fdd837c6de3a7055267af3af0e6 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 21 Nov 2024 15:35:45 +0100 Subject: [PATCH 196/605] Posts DataViews: Refactor the router to use route registration (#67160) Co-authored-by: youknowriad Co-authored-by: ntsekouras --- .../src/components/posts-app-routes/home.js | 36 ++++++++++ .../src/components/posts-app-routes/index.js | 36 ++++++++++ .../components/posts-app-routes/posts-edit.js | 31 +++++++++ .../posts-list-view-quick-edit.js | 52 ++++++++++++++ .../posts-app-routes/posts-list-view.js | 40 +++++++++++ .../posts-app-routes/posts-view-quick-edit.js | 49 +++++++++++++ .../components/posts-app-routes/posts-view.js | 35 ++++++++++ .../src/components/posts-app/index.js | 4 +- .../src/components/posts-app/router.js | 69 ------------------- 9 files changed, 282 insertions(+), 70 deletions(-) create mode 100644 packages/edit-site/src/components/posts-app-routes/home.js create mode 100644 packages/edit-site/src/components/posts-app-routes/index.js create mode 100644 packages/edit-site/src/components/posts-app-routes/posts-edit.js create mode 100644 packages/edit-site/src/components/posts-app-routes/posts-list-view-quick-edit.js create mode 100644 packages/edit-site/src/components/posts-app-routes/posts-list-view.js create mode 100644 packages/edit-site/src/components/posts-app-routes/posts-view-quick-edit.js create mode 100644 packages/edit-site/src/components/posts-app-routes/posts-view.js delete mode 100644 packages/edit-site/src/components/posts-app/router.js diff --git a/packages/edit-site/src/components/posts-app-routes/home.js b/packages/edit-site/src/components/posts-app-routes/home.js new file mode 100644 index 00000000000000..ec99cbd8899f1d --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/home.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; +import { unlock } from '../../lock-unlock'; + +const { useLocation } = unlock( routerPrivateApis ); + +function HomeMobileView() { + const { params = {} } = useLocation(); + const { canvas = 'view' } = params; + + return canvas === 'edit' ? ( + + ) : ( + + ); +} + +export const homeRoute = { + name: 'home', + match: () => { + return true; + }, + areas: { + sidebar: , + preview: , + mobile: HomeMobileView, + }, +}; diff --git a/packages/edit-site/src/components/posts-app-routes/index.js b/packages/edit-site/src/components/posts-app-routes/index.js new file mode 100644 index 00000000000000..e850bbd382200d --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/index.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { useRegistry, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { store as siteEditorStore } from '../../store'; +import { homeRoute } from './home'; +import { postsListViewQuickEditRoute } from './posts-list-view-quick-edit'; +import { postsListViewRoute } from './posts-list-view'; +import { postsViewQuickEditRoute } from './posts-view-quick-edit'; +import { postsViewRoute } from './posts-view'; +import { postsEditRoute } from './posts-edit'; + +const routes = [ + postsListViewQuickEditRoute, + postsListViewRoute, + postsViewQuickEditRoute, + postsViewRoute, + postsEditRoute, + homeRoute, +]; + +export function useRegisterPostsAppRoutes() { + const registry = useRegistry(); + const { registerRoute } = unlock( useDispatch( siteEditorStore ) ); + useEffect( () => { + registry.batch( () => { + routes.forEach( registerRoute ); + } ); + }, [ registry, registerRoute ] ); +} diff --git a/packages/edit-site/src/components/posts-app-routes/posts-edit.js b/packages/edit-site/src/components/posts-app-routes/posts-edit.js new file mode 100644 index 00000000000000..d3958245595416 --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/posts-edit.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import Editor from '../editor'; + +export const postsEditRoute = { + name: 'posts-edit', + match: ( params ) => { + return params.postType === 'post' && params.canvas === 'edit'; + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + preview: , + }, +}; diff --git a/packages/edit-site/src/components/posts-app-routes/posts-list-view-quick-edit.js b/packages/edit-site/src/components/posts-app-routes/posts-list-view-quick-edit.js new file mode 100644 index 00000000000000..d2434b390ffd9f --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/posts-list-view-quick-edit.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import { unlock } from '../../lock-unlock'; +import { PostEdit } from '../post-edit'; +import Editor from '../editor'; + +const { useLocation } = unlock( routerPrivateApis ); + +function PostQuickEdit() { + const { params } = useLocation(); + return ; +} + +export const postsListViewQuickEditRoute = { + name: 'posts-list-view-quick-edit', + match: ( params ) => { + return ( + params.isCustom !== 'true' && + ( params.layout ?? 'list' ) === 'list' && + !! params.quickEdit && + params.postType === 'post' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + preview: , + edit: , + }, + widths: { + content: 380, + edit: 380, + }, +}; diff --git a/packages/edit-site/src/components/posts-app-routes/posts-list-view.js b/packages/edit-site/src/components/posts-app-routes/posts-list-view.js new file mode 100644 index 00000000000000..68aa86c7fb2392 --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/posts-list-view.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import Editor from '../editor'; + +export const postsListViewRoute = { + name: 'posts-list-view', + match: ( params ) => { + return ( + params.isCustom !== 'true' && + ( params.layout ?? 'list' ) === 'list' && + ! params.quickEdit && + params.postType === 'post' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + preview: , + mobile: , + }, + widths: { + content: 380, + }, +}; diff --git a/packages/edit-site/src/components/posts-app-routes/posts-view-quick-edit.js b/packages/edit-site/src/components/posts-app-routes/posts-view-quick-edit.js new file mode 100644 index 00000000000000..52e6f9a2d26ef6 --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/posts-view-quick-edit.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import { unlock } from '../../lock-unlock'; +import { PostEdit } from '../post-edit'; + +const { useLocation } = unlock( routerPrivateApis ); + +function PostQuickEdit() { + const { params } = useLocation(); + return ; +} + +export const postsViewQuickEditRoute = { + name: 'posts-view-quick-edit', + match: ( params ) => { + return ( + ( params.isCustom === 'true' || + ( params.layout ?? 'list' ) !== 'list' ) && + !! params.quickEdit && + params.postType === 'post' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + edit: , + }, + widths: { + edit: 380, + }, +}; diff --git a/packages/edit-site/src/components/posts-app-routes/posts-view.js b/packages/edit-site/src/components/posts-app-routes/posts-view.js new file mode 100644 index 00000000000000..6559991475d278 --- /dev/null +++ b/packages/edit-site/src/components/posts-app-routes/posts-view.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; + +export const postsViewRoute = { + name: 'posts-view', + match: ( params ) => { + return ( + ( params.isCustom === 'true' || + ( params.layout ?? 'list' ) !== 'list' ) && + ! params.quickEdit && + params.postType === 'post' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/components/posts-app/index.js b/packages/edit-site/src/components/posts-app/index.js index 80d2c1c7eba86f..72e5b1eb997498 100644 --- a/packages/edit-site/src/components/posts-app/index.js +++ b/packages/edit-site/src/components/posts-app/index.js @@ -11,13 +11,15 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; * Internal dependencies */ import Layout from '../layout'; -import useActiveRoute from './router'; +import { useRegisterPostsAppRoutes } from '../posts-app-routes'; import { unlock } from '../../lock-unlock'; +import useActiveRoute from '../layout/router'; const { RouterProvider } = unlock( routerPrivateApis ); const { GlobalStylesProvider } = unlock( editorPrivateApis ); function PostsLayout() { + useRegisterPostsAppRoutes(); const route = useActiveRoute(); return ; } diff --git a/packages/edit-site/src/components/posts-app/router.js b/packages/edit-site/src/components/posts-app/router.js deleted file mode 100644 index de89567b262094..00000000000000 --- a/packages/edit-site/src/components/posts-app/router.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * WordPress dependencies - */ -import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; - -/** - * Internal dependencies - */ -import { unlock } from '../../lock-unlock'; -import Editor from '../editor'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; -import DataViewsSidebarContent from '../sidebar-dataviews'; -import PostList from '../post-list'; - -const { useLocation } = unlock( routerPrivateApis ); - -export default function useActiveRoute() { - const { params = {} } = useLocation(); - const { postType, layout, canvas } = params; - const labels = useSelect( - ( select ) => { - return select( coreStore ).getPostType( postType )?.labels; - }, - [ postType ] - ); - - // Posts list. - if ( [ 'post' ].includes( postType ) ) { - const isListLayout = layout === 'list' || ! layout; - return { - name: 'posts-list', - areas: { - sidebar: ( - } - /> - ), - content: , - preview: ( isListLayout || canvas === 'edit' ) && ( - - ), - mobile: - canvas === 'edit' ? ( - - ) : ( - - ), - }, - widths: { - content: isListLayout ? 380 : undefined, - }, - }; - } - - // Fallback shows the home page preview - return { - name: 'default', - areas: { - sidebar: , - preview: , - mobile: canvas === 'edit' && , - }, - }; -} From e0d5ee65919d8e187509f7a78b9b77255e63ddac Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:31:20 -0500 Subject: [PATCH 197/605] Migrate Gradle wrapper validation action. (#66602) * Migrate Gradel wrapper validation action. The `gradle/wrapper-validation-action` has been migrated to `gradle/actions/wrapper-validation`. Read more: https://github.com/gradle/actions/blob/main/docs/deprecation-upgrade-guide.md#the-action-gradlewrapper-validation-action-has-been-replaced-by-gradleactionswrapper-validation. * FIx botched merge. * Update gradle/actions version. --------- Co-authored-by: desrosj --- .github/workflows/gradle-wrapper-validation.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 54378765bd26ff..2bb5676ae9ed66 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -6,7 +6,9 @@ jobs: name: 'Validation' runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - - uses: gradle/wrapper-validation-action@v3 + - name: Validate checksums + uses: gradle/actions/wrapper-validation@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1 From 9a6f48b8337e598076503b7ad36e10e52e666f94 Mon Sep 17 00:00:00 2001 From: Himanshu Pathak Date: Thu, 21 Nov 2024 23:37:13 +0530 Subject: [PATCH 198/605] Menu.ItemHelpText: better line breaking (#67011) * Fix: Use break-word instead of break-all in attribute description Changed break-all to use word-break for better readability. * CSS: Replace deprecated word-break with overflow-wrap Replace deprecated `word-break: break-word` with the modern equivalent `overflow-wrap: anywhere` to handle text wrapping. This change maintains the same text wrapping behavior while using the current standard CSS property. * Docs: Add changelog entry for MenuItemHelpText text wrapping fix * Docs: Update changelog entry for Menu.ItemHelpText to internal section Co-authored-by: himanshupathak95 Co-authored-by: juanfra Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo Co-authored-by: im3dabasia Co-authored-by: jasmussen --- packages/components/CHANGELOG.md | 1 + packages/components/src/menu/styles.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index ff4a8b714c9a52..d7741f4caa9886 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -35,6 +35,7 @@ - `SlotFill`: fix dependencies of `Fill` registration effects ([#67071](https://github.com/WordPress/gutenberg/pull/67071)). - `SlotFill`: rewrite the `Slot` component from class component to functional ([#67153](https://github.com/WordPress/gutenberg/pull/67153)). +- `Menu.ItemHelpText`: Fix text wrapping to prevent unintended word breaks ([#67011](https://github.com/WordPress/gutenberg/pull/67011)). ## 28.12.0 (2024-11-16) diff --git a/packages/components/src/menu/styles.ts b/packages/components/src/menu/styles.ts index 3312c8cb2de161..0e0752bf24cd10 100644 --- a/packages/components/src/menu/styles.ts +++ b/packages/components/src/menu/styles.ts @@ -380,7 +380,7 @@ export const MenuItemHelpText = styled( Truncate )` font-size: ${ font( 'helpText.fontSize' ) }; line-height: 16px; color: ${ LIGHTER_TEXT_COLOR }; - word-break: break-all; + overflow-wrap: anywhere; [data-active-item]:not( [data-focus-visible] ) *:not( ${ MenuPopoverInnerWrapper } ) From 98a7d5d10e86fdbca135f46233a08d6cf9acfcc8 Mon Sep 17 00:00:00 2001 From: Michal Date: Thu, 21 Nov 2024 19:49:08 -0500 Subject: [PATCH 199/605] Update the enhanced pagination help text (#67173) --- .../inspector-controls/enhanced-pagination-control.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js b/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js index 9d47d67e61d781..4d0b1a9fd9da37 100644 --- a/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js +++ b/packages/block-library/src/query/edit/inspector-controls/enhanced-pagination-control.js @@ -18,15 +18,13 @@ export default function EnhancedPaginationControl( { const fullPageClientSideNavigation = window.__experimentalFullPageClientSideNavigation; - let help = __( 'Browsing between pages requires a full page reload.' ); + let help = __( + 'Reload the full page—instead of just the posts list—when visitors navigate between pages.' + ); if ( fullPageClientSideNavigation ) { help = __( 'Experimental full-page client-side navigation setting enabled.' ); - } else if ( enhancedPagination ) { - help = __( - 'Reload the full page—instead of just the posts list—when visitors navigate between pages.' - ); } else if ( hasUnsupportedBlocks ) { help = __( 'Enhancement disabled because there are non-compatible blocks inside the Query block.' From a7c373f7acf212ec7999db53ddb17108f1f684df Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 22 Nov 2024 09:48:38 +0800 Subject: [PATCH 200/605] Avoid zooming out when browsing styles if the preview mode is active (#67190) Co-authored-by: talldan Co-authored-by: tellthemachines Co-authored-by: ramonjd Co-authored-by: draganescu Co-authored-by: yogeshbhutkar Co-authored-by: jameskoster --- .../global-styles/screen-style-variations.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/screen-style-variations.js b/packages/edit-site/src/components/global-styles/screen-style-variations.js index 76d13023ff09a8..f6036aaace4358 100644 --- a/packages/edit-site/src/components/global-styles/screen-style-variations.js +++ b/packages/edit-site/src/components/global-styles/screen-style-variations.js @@ -1,10 +1,13 @@ /** * WordPress dependencies */ +import { + privateApis as blockEditorPrivateApis, + store as blockEditorStore, +} from '@wordpress/block-editor'; import { Card, CardBody } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; -import { useDispatch } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; import { useEffect } from '@wordpress/element'; @@ -19,10 +22,13 @@ const { useZoomOut } = unlock( blockEditorPrivateApis ); function ScreenStyleVariations() { // Style Variations should only be previewed in with - // - a "zoomed out" editor + // - a "zoomed out" editor (but not when in preview mode) // - "Desktop" device preview + const isPreviewMode = useSelect( ( select ) => { + return select( blockEditorStore ).getSettings().isPreviewMode; + }, [] ); const { setDeviceType } = useDispatch( editorStore ); - useZoomOut(); + useZoomOut( ! isPreviewMode ); useEffect( () => { setDeviceType( 'desktop' ); }, [ setDeviceType ] ); From 92254bf3677d7c561d36ab47ec0344e18f71fa2b Mon Sep 17 00:00:00 2001 From: Doug Wollison Date: Thu, 21 Nov 2024 21:40:17 -0500 Subject: [PATCH 201/605] Details block: use summary content as default label (#67217) Co-authored-by: dougwollison Co-authored-by: talldan --- packages/block-library/src/details/index.js | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/details/index.js b/packages/block-library/src/details/index.js index 3ba5efb04e27d2..31ee2e0e00f139 100644 --- a/packages/block-library/src/details/index.js +++ b/packages/block-library/src/details/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { details as icon } from '@wordpress/icons'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -34,6 +34,28 @@ export const settings = { }, ], }, + __experimentalLabel( attributes, { context } ) { + const { summary } = attributes; + + const customName = attributes?.metadata?.name; + const hasSummary = summary?.trim().length > 0; + + // In the list view, use the block's summary as the label. + // If the summary is empty, fall back to the default label. + if ( context === 'list-view' && ( customName || hasSummary ) ) { + return customName || summary; + } + + if ( context === 'accessibility' ) { + return ! hasSummary + ? __( 'Details. Empty.' ) + : sprintf( + /* translators: accessibility text; summary title. */ + __( 'Details. %s' ), + summary + ); + } + }, save, edit, transforms, From b58cce696977b0f8850a39bfaf895a426a59c619 Mon Sep 17 00:00:00 2001 From: Manzoor Wani Date: Fri, 22 Nov 2024 00:30:15 -0800 Subject: [PATCH 202/605] Fix ESLint Jest reporting entire body of the test function rather than the identifier (#67222) Co-authored-by: manzoorwanijk Co-authored-by: Mamaduka Co-authored-by: talldan --- package-lock.json | 11 ++++++----- package.json | 2 +- packages/eslint-plugin/package.json | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04a8d2021f12a0..1fc9131a33c8c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,7 +88,7 @@ "eslint-import-resolver-node": "0.3.4", "eslint-plugin-eslint-comments": "3.1.2", "eslint-plugin-import": "2.25.2", - "eslint-plugin-jest": "27.2.3", + "eslint-plugin-jest": "27.4.3", "eslint-plugin-jest-dom": "5.0.2", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-react-compiler": "19.0.0-beta-0dec889-20241115", @@ -25190,9 +25190,10 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/eslint-plugin-jest": { - "version": "27.2.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz", - "integrity": "sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==", + "version": "27.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.4.3.tgz", + "integrity": "sha512-7S6SmmsHsgIm06BAGCAxL+ABd9/IB3MWkz2pudj6Qqor2y1qQpWPfuFU4SG9pWj4xDjF0e+D7Llh5useuSzAZw==", + "license": "MIT", "dependencies": { "@typescript-eslint/utils": "^5.10.0" }, @@ -54649,7 +54650,7 @@ "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jest": "^27.2.3", + "eslint-plugin-jest": "^27.4.3", "eslint-plugin-jsdoc": "^46.4.6", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-playwright": "^0.15.3", diff --git a/package.json b/package.json index 52668f667c71cd..41102f867f0e22 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "eslint-import-resolver-node": "0.3.4", "eslint-plugin-eslint-comments": "3.1.2", "eslint-plugin-import": "2.25.2", - "eslint-plugin-jest": "27.2.3", + "eslint-plugin-jest": "27.4.3", "eslint-plugin-jest-dom": "5.0.2", "eslint-plugin-prettier": "5.0.0", "eslint-plugin-react-compiler": "19.0.0-beta-0dec889-20241115", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index c3521692d7fd12..0e8880f0941234 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -39,7 +39,7 @@ "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jest": "^27.2.3", + "eslint-plugin-jest": "^27.4.3", "eslint-plugin-jsdoc": "^46.4.6", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-playwright": "^0.15.3", From 58ae911e10abe260a20514c2249ff50b8e46dcac Mon Sep 17 00:00:00 2001 From: Sainath Poojary <53347682+SainathPoojary@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:22:22 +0530 Subject: [PATCH 203/605] Social Links: Fix font family and weight inconsistency in editor (#67204) Added font-weight: inherit; and font-family: inherit; to the .wp-block-social-link-anchor class in the editor to ensure consistent font styling with the frontend. Unlinked contributors: bgardner. Co-authored-by: SainathPoojary Co-authored-by: akasunil Co-authored-by: Mamaduka Co-authored-by: jordesign Co-authored-by: himanshupathak95 --- packages/block-library/src/social-link/editor.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-library/src/social-link/editor.scss b/packages/block-library/src/social-link/editor.scss index 2f5714f5d2ed9f..2e93c9447cca8d 100644 --- a/packages/block-library/src/social-link/editor.scss +++ b/packages/block-library/src/social-link/editor.scss @@ -14,6 +14,8 @@ font-size: inherit; color: currentColor; height: auto; + font-weight: inherit; + font-family: inherit; // This rule ensures social link buttons display correctly in template parts. opacity: 1; From 81d2ee840c21bf7818ef0b3ec0a6297ace43d973 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 22 Nov 2024 09:17:00 +0000 Subject: [PATCH 204/605] Use rems for Nav overlay left padding (#67168) Co-authored-by: getdave Co-authored-by: MaggieCabrera Co-authored-by: tysonlmao --- packages/block-library/src/navigation/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 3d7a8810ae9b71..5f49eba466a5fd 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -510,7 +510,7 @@ button.wp-block-navigation-item__content { padding-top: clamp(1rem, var(--wp--style--root--padding-top), 20rem); padding-right: clamp(1rem, var(--wp--style--root--padding-right), 20rem); padding-bottom: clamp(1rem, var(--wp--style--root--padding-bottom), 20rem); - padding-left: clamp(1rem, var(--wp--style--root--padding-left), 20em); + padding-left: clamp(1rem, var(--wp--style--root--padding-left), 20rem); // Allow modal to scroll. overflow: auto; From 2919c5c4c6ee20740faa7bd7ffd9b360120e6a38 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Fri, 22 Nov 2024 10:47:03 +0100 Subject: [PATCH 205/605] Fix fatal error in in_array call in post_type_default_rendering_mode (#67225) Co-authored-by: jsnajdr Co-authored-by: fabiankaegy --- lib/compat/wordpress-6.8/post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.8/post.php b/lib/compat/wordpress-6.8/post.php index 26e6c3adc07a3d..639e33b4e5ca51 100644 --- a/lib/compat/wordpress-6.8/post.php +++ b/lib/compat/wordpress-6.8/post.php @@ -39,7 +39,7 @@ function gutenberg_post_type_default_rendering_mode( $args, $post_type ) { if ( wp_is_block_theme() && ( isset( $args['show_in_rest'] ) && $args['show_in_rest'] ) && - ( isset( $args['supports'] ) && in_array( 'editor', $args['supports'], true ) ) + ( ! empty( $args['supports'] ) && in_array( 'editor', $args['supports'], true ) ) ) { // Validate the supplied rendering mode. if ( From 4d2fb64425d7653315997d9c4003f1fa6a4f912a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:24:12 +0100 Subject: [PATCH 206/605] DataViews: fix spacing when combining combined fields (#67226) Co-authored-by: oandregal Co-authored-by: youknowriad --- .../components/dataviews/stories/fixtures.tsx | 1 + .../dataviews/stories/index.story.tsx | 18 +++++++++++++++--- .../src/dataviews-layouts/table/index.tsx | 6 +++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/dataviews/src/components/dataviews/stories/fixtures.tsx b/packages/dataviews/src/components/dataviews/stories/fixtures.tsx index ff098209b34684..6c74c516853c5b 100644 --- a/packages/dataviews/src/components/dataviews/stories/fixtures.tsx +++ b/packages/dataviews/src/components/dataviews/stories/fixtures.tsx @@ -558,6 +558,7 @@ export const themeFields: Field< Theme >[] = [ }, { id: 'requires', label: 'Requires at least' }, { id: 'tested', label: 'Tested up to' }, + { id: 'icon', label: 'Icon', render: () => }, { id: 'tags', label: 'Tags', diff --git a/packages/dataviews/src/components/dataviews/stories/index.story.tsx b/packages/dataviews/src/components/dataviews/stories/index.story.tsx index 878677d2eb30c6..6161af125b5ca4 100644 --- a/packages/dataviews/src/components/dataviews/stories/index.story.tsx +++ b/packages/dataviews/src/components/dataviews/stories/index.story.tsx @@ -137,16 +137,28 @@ export const FieldsNoSortableNoHidable = () => { export const CombinedFields = () => { const defaultLayoutsThemes = { table: { - fields: [ 'theme', 'requires', 'tested' ], + fields: [ 'theme_with_combined', 'theme_with_simple' ], layout: { primaryField: 'name', combinedFields: [ { - id: 'theme', + id: 'name_tested', label: 'Theme', - children: [ 'name', 'description' ], + children: [ 'name', 'tested' ], direction: 'vertical', }, + { + id: 'theme_with_combined', + label: 'Combine combined fields', + children: [ 'icon', 'name_tested' ], + direction: 'horizontal', + }, + { + id: 'theme_with_simple', + label: 'Combine simple fields', + children: [ 'icon', 'name' ], + direction: 'horizontal', + }, ] as CombinedField[], styles: { theme: { diff --git a/packages/dataviews/src/dataviews-layouts/table/index.tsx b/packages/dataviews/src/dataviews-layouts/table/index.tsx index 8ef41db1c38798..db76d24b53bfa3 100644 --- a/packages/dataviews/src/dataviews-layouts/table/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/table/index.tsx @@ -147,7 +147,11 @@ function TableColumnCombined< Item >( { ) ); if ( field.direction === 'horizontal' ) { - return { children }; + return ( + + { children } + + ); } return { children }; } From 452de1b775ea28e4d13f94db6117b99e36d56e0e Mon Sep 17 00:00:00 2001 From: Akshat Kakkad <87222220+AKSHAT2802@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:09:41 +0530 Subject: [PATCH 207/605] Add all color palettes to select from editor panel (#65148) * Add all the colors in with-color-context so that they all are listed in block editor * Remove default colour pallete * Remove extra space * Update packages/block-editor/src/components/color-palette/with-color-context.js re-add default colours Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> * Add default colours if enabled * Update packages/block-editor/src/components/color-palette/with-color-context.js Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> * update test --------- Unlinked contributors: EddyBoels, burnuser. Co-authored-by: AKSHAT2802 Co-authored-by: richtabor Co-authored-by: t-hamano Co-authored-by: ndiego Co-authored-by: rohitmathur-7 Co-authored-by: javiercasares --- .../color-palette/with-color-context.js | 32 +++++++++++++++---- packages/block-library/src/cover/test/edit.js | 4 +-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/color-palette/with-color-context.js b/packages/block-editor/src/components/color-palette/with-color-context.js index 62b8c1bc4b6181..38c90531edaac1 100644 --- a/packages/block-editor/src/components/color-palette/with-color-context.js +++ b/packages/block-editor/src/components/color-palette/with-color-context.js @@ -10,14 +10,32 @@ import { useSettings } from '../use-settings'; export default createHigherOrderComponent( ( WrappedComponent ) => { return ( props ) => { - const [ colorsFeature, enableCustomColors ] = useSettings( - 'color.palette', - 'color.custom' + // Get the default colors, theme colors, and custom colors + const [ + defaultColors, + themeColors, + customColors, + enableCustomColors, + enableDefaultColors, + ] = useSettings( + 'color.palette.default', + 'color.palette.theme', + 'color.palette.custom', + 'color.custom', + 'color.defaultPalette' ); - const { - colors = colorsFeature, - disableCustomColors = ! enableCustomColors, - } = props; + + const _colors = enableDefaultColors + ? [ + ...( themeColors || [] ), + ...( defaultColors || [] ), + ...( customColors || [] ), + ] + : [ ...( themeColors || [] ), ...( customColors || [] ) ]; + + const { colors = _colors, disableCustomColors = ! enableCustomColors } = + props; + const hasColorsToChoose = ( colors && colors.length > 0 ) || ! disableCustomColors; return ( diff --git a/packages/block-library/src/cover/test/edit.js b/packages/block-library/src/cover/test/edit.js index 5c1a5b5e13e67d..f5d6a5301ef6d2 100644 --- a/packages/block-library/src/cover/test/edit.js +++ b/packages/block-library/src/cover/test/edit.js @@ -337,7 +337,7 @@ describe( 'Cover block', () => { describe( 'when colors are disabled', () => { test( 'does not render overlay control', async () => { await setup( undefined, true, disabledColorSettings ); - await createAndSelectBlock(); + await selectBlock( 'Block: Cover' ); await userEvent.click( screen.getByRole( 'tab', { name: 'Styles' } ) ); @@ -350,7 +350,7 @@ describe( 'Cover block', () => { } ); test( 'does not render opacity control', async () => { await setup( undefined, true, disabledColorSettings ); - await createAndSelectBlock(); + await selectBlock( 'Block: Cover' ); await userEvent.click( screen.getByRole( 'tab', { name: 'Styles' } ) ); From 455bb5d93408c45622d747a547088cd77e0d0511 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Fri, 22 Nov 2024 12:47:04 +0100 Subject: [PATCH 208/605] Modal: Increase size of the Close button (#66792) * Make the modal component close button use the compact size. * Make spacing consistent. * Add changelog entry. Co-authored-by: afercia Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: jameskoster --- packages/block-library/src/freeform/modal.js | 2 +- packages/components/CHANGELOG.md | 1 + packages/components/src/modal/index.tsx | 4 ++-- packages/components/src/modal/stories/index.story.tsx | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/freeform/modal.js b/packages/block-library/src/freeform/modal.js index 4ed4ef4d3a07ca..35652eb29948a3 100644 --- a/packages/block-library/src/freeform/modal.js +++ b/packages/block-library/src/freeform/modal.js @@ -25,7 +25,7 @@ function ModalAuxiliaryActions( { onClick, isModalFullScreen } ) { return (