Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] trunk from WordPress:trunk #77

Merged
merged 9 commits into from
Dec 11, 2024
Merged
28 changes: 28 additions & 0 deletions assets/blueprints/blueprint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"landingPage": "/wp-admin/post.php?post=1&action=edit",
"plugins": [ "gutenberg" ],
"login": true,
"features": {
"networking": true
},
"preferredVersions": {
"php": "latest",
"wp": "latest"
},
"steps": [
{
"step": "setSiteOptions",
"options": {
"blogname": "Testing Gutenberg"
}
},
{
"step": "updateUserMeta",
"meta": {
"admin_color": "modern"
},
"userId": 1
}
]
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@
"build:packages": "npm run --silent build:package-types && node ./bin/packages/build.js",
"postbuild:packages": " npm run --if-present --workspaces build:wp",
"build:plugin-zip": "bash ./bin/build-plugin-zip.sh",
"clean:package-types": "tsc --build --clean && rimraf \"./packages/*/build-types\"",
"clean:packages": "rimraf \"./packages/*/@(build|build-module|build-wp|build-style)\"",
"clean:package-types": "tsc --build --clean && rimraf --glob './packages/*/build-types'",
"clean:packages": "rimraf --glob './packages/*/@(build|build-module|build-wp|build-style)'",
"component-usage-stats": "node ./node_modules/react-scanner/bin/react-scanner -c ./react-scanner.config.js",
"dev": "cross-env NODE_ENV=development npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"",
"dev:packages": "cross-env NODE_ENV=development concurrently \"node ./bin/packages/watch.js\" \"tsc --build --watch\"",
Expand All @@ -193,7 +193,7 @@
"docs:gen": "node ./docs/tool/index.js",
"docs:theme-ref": "node ./bin/api-docs/gen-theme-reference.mjs",
"env": "wp-env",
"fixtures:clean": "rimraf \"test/integration/fixtures/blocks/*.+(json|serialized.html)\"",
"fixtures:clean": "rimraf --glob 'test/integration/fixtures/blocks/*.+(json|serialized.html)'",
"fixtures:generate": "cross-env GENERATE_MISSING_FIXTURES=y npm run test:unit test/integration/full-content/ && npm run format test/integration/fixtures/blocks/*.json",
"fixtures:regenerate": "npm-run-all fixtures:clean fixtures:generate",
"format": "wp-scripts format",
Expand Down
229 changes: 218 additions & 11 deletions packages/block-editor/src/components/block-edit/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@ import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { withFilters } from '@wordpress/components';
import {
getBlockDefaultClassName,
hasBlockSupport,
getBlockType,
hasBlockSupport,
store as blocksStore,
} from '@wordpress/blocks';
import { useContext, useMemo } from '@wordpress/element';
import { withFilters } from '@wordpress/components';
import { useRegistry, useSelect } from '@wordpress/data';
import { useCallback, useContext, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import BlockContext from '../block-context';
import isURLLike from '../link-control/is-url-like';
import {
canBindAttribute,
hasPatternOverridesDefaultBinding,
replacePatternOverridesDefaultBinding,
} from '../../utils/block-bindings';
import { unlock } from '../../lock-unlock';

/**
* Default value used for blocks which do not define their own context needs,
Expand Down Expand Up @@ -48,27 +57,223 @@ const Edit = ( props ) => {
const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit );

const EditWithGeneratedProps = ( props ) => {
const { attributes = {}, name } = props;
const { name, clientId, attributes, setAttributes } = props;
const registry = useRegistry();
const blockType = getBlockType( name );
const blockContext = useContext( BlockContext );
const registeredSources = useSelect(
( select ) =>
unlock( select( blocksStore ) ).getAllBlockBindingsSources(),
[]
);

// Assign context values using the block type's declared context needs.
const context = useMemo( () => {
return blockType && blockType.usesContext
const { blockBindings, context, hasPatternOverrides } = useMemo( () => {
// Assign context values using the block type's declared context needs.
const computedContext = blockType?.usesContext
? Object.fromEntries(
Object.entries( blockContext ).filter( ( [ key ] ) =>
blockType.usesContext.includes( key )
)
)
: DEFAULT_BLOCK_CONTEXT;
}, [ blockType, blockContext ] );
// Add context requested by Block Bindings sources.
if ( attributes?.metadata?.bindings ) {
Object.values( attributes?.metadata?.bindings || {} ).forEach(
( binding ) => {
registeredSources[ binding?.source ]?.usesContext?.forEach(
( key ) => {
computedContext[ key ] = blockContext[ key ];
}
);
}
);
}
return {
blockBindings: replacePatternOverridesDefaultBinding(
name,
attributes?.metadata?.bindings
),
context: computedContext,
hasPatternOverrides: hasPatternOverridesDefaultBinding(
attributes?.metadata?.bindings
),
};
}, [
name,
blockType?.usesContext,
blockContext,
attributes?.metadata?.bindings,
registeredSources,
] );

const computedAttributes = useSelect(
( select ) => {
if ( ! blockBindings ) {
return attributes;
}

const attributesFromSources = {};
const blockBindingsBySource = new Map();

for ( const [ attributeName, binding ] of Object.entries(
blockBindings
) ) {
const { source: sourceName, args: sourceArgs } = binding;
const source = registeredSources[ sourceName ];
if ( ! source || ! canBindAttribute( name, attributeName ) ) {
continue;
}

blockBindingsBySource.set( source, {
...blockBindingsBySource.get( source ),
[ attributeName ]: {
args: sourceArgs,
},
} );
}

if ( blockBindingsBySource.size ) {
for ( const [ source, bindings ] of blockBindingsBySource ) {
// Get values in batch if the source supports it.
let values = {};
if ( ! source.getValues ) {
Object.keys( bindings ).forEach( ( attr ) => {
// Default to the the source label when `getValues` doesn't exist.
values[ attr ] = source.label;
} );
} else {
values = source.getValues( {
select,
context,
clientId,
bindings,
} );
}
for ( const [ attributeName, value ] of Object.entries(
values
) ) {
if (
attributeName === 'url' &&
( ! value || ! isURLLike( value ) )
) {
// Return null if value is not a valid URL.
attributesFromSources[ attributeName ] = null;
} else {
attributesFromSources[ attributeName ] = value;
}
}
}
}

return {
...attributes,
...attributesFromSources,
};
},
[
attributes,
blockBindings,
clientId,
context,
name,
registeredSources,
]
);

const setBoundAttributes = useCallback(
( nextAttributes ) => {
if ( ! blockBindings ) {
setAttributes( nextAttributes );
return;
}

registry.batch( () => {
const keptAttributes = { ...nextAttributes };
const blockBindingsBySource = new Map();

// Loop only over the updated attributes to avoid modifying the bound ones that haven't changed.
for ( const [ attributeName, newValue ] of Object.entries(
keptAttributes
) ) {
if (
! blockBindings[ attributeName ] ||
! canBindAttribute( name, attributeName )
) {
continue;
}

const binding = blockBindings[ attributeName ];
const source = registeredSources[ binding?.source ];
if ( ! source?.setValues ) {
continue;
}
blockBindingsBySource.set( source, {
...blockBindingsBySource.get( source ),
[ attributeName ]: {
args: binding.args,
newValue,
},
} );
delete keptAttributes[ attributeName ];
}

if ( blockBindingsBySource.size ) {
for ( const [
source,
bindings,
] of blockBindingsBySource ) {
source.setValues( {
select: registry.select,
dispatch: registry.dispatch,
context,
clientId,
bindings,
} );
}
}

const hasParentPattern = !! context[ 'pattern/overrides' ];

if (
// Don't update non-connected attributes if the block is using pattern overrides
// and the editing is happening while overriding the pattern (not editing the original).
! ( hasPatternOverrides && hasParentPattern ) &&
Object.keys( keptAttributes ).length
) {
// Don't update caption and href until they are supported.
if ( hasPatternOverrides ) {
delete keptAttributes.caption;
delete keptAttributes.href;
}
setAttributes( keptAttributes );
}
} );
},
[
blockBindings,
clientId,
context,
hasPatternOverrides,
setAttributes,
registeredSources,
name,
registry,
]
);

if ( ! blockType ) {
return null;
}

if ( blockType.apiVersion > 1 ) {
return <EditWithFilters { ...props } context={ context } />;
return (
<EditWithFilters
{ ...props }
attributes={ computedAttributes }
context={ context }
setAttributes={ setBoundAttributes }
/>
);
}

// Generate a class name for the block's editable form.
Expand All @@ -77,15 +282,17 @@ const EditWithGeneratedProps = ( props ) => {
: null;
const className = clsx(
generatedClassName,
attributes.className,
attributes?.className,
props.className
);

return (
<EditWithFilters
{ ...props }
context={ context }
attributes={ computedAttributes }
className={ className }
context={ context }
setAttributes={ setBoundAttributes }
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { useBlockRefProvider } from './use-block-refs';
import { useIntersectionObserver } from './use-intersection-observer';
import { useScrollIntoView } from './use-scroll-into-view';
import { useFlashEditableBlocks } from '../../use-flash-editable-blocks';
import { canBindBlock } from '../../../hooks/use-bindings-attributes';
import { canBindBlock } from '../../../utils/block-bindings';
import { useFirefoxDraggableCompatibility } from './use-firefox-draggable-compatibility';

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import FormatEdit from './format-edit';
import { getAllowedFormats } from './utils';
import { Content, valueToHTMLString } from './content';
import { withDeprecations } from './with-deprecations';
import { canBindBlock } from '../../hooks/use-bindings-attributes';
import { canBindBlock } from '../../utils/block-bindings';
import BlockContext from '../block-context';

export const keyboardShortcutContext = createContext();
Expand Down
4 changes: 2 additions & 2 deletions packages/block-editor/src/hooks/block-bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ import { useViewportMatch } from '@wordpress/compose';
import {
canBindAttribute,
getBindableAttributes,
} from '../hooks/use-bindings-attributes';
useBlockBindingsUtils,
} from '../utils/block-bindings';
import { unlock } from '../lock-unlock';
import InspectorControls from '../components/inspector-controls';
import BlockContext from '../components/block-context';
import { useBlockEditContext } from '../components/block-edit';
import { useBlockBindingsUtils } from '../utils/block-bindings';
import { store as blockEditorStore } from '../store';

const { Menu } = unlock( componentsPrivateApis );
Expand Down
1 change: 0 additions & 1 deletion packages/block-editor/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import './metadata';
import blockHooks from './block-hooks';
import blockBindingsPanel from './block-bindings';
import './block-renaming';
import './use-bindings-attributes';
import './grid-visualizer';

createBlockEditFilter(
Expand Down
Loading
Loading