Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions packages/block-editor/src/store/private-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,48 @@ export function isContainerInsertableToInContentOnlyMode(
);
}

function getClientIdWithClientIdsTreeUnmemoized( state, clientId ) {
return {
clientId,
innerBlocks: getClientIdsTreeUnmemoized( state, clientId ),
};
}

function getClientIdsTreeUnmemoized( state, rootClientId = '' ) {
return getBlockOrder( state, rootClientId ).map( ( clientId ) =>
getClientIdWithClientIdsTreeUnmemoized( state, clientId )
);
}

/**
* Returns a stripped down block object containing only its client ID,
* and its inner blocks' client IDs.
*
* @param {Object} state Editor state.
* @param {string} clientId Client ID of the block to get.
*
* @return {Object} Client IDs of the post blocks.
*/
export const getClientIdWithClientIdsTree = createSelector(
getClientIdWithClientIdsTreeUnmemoized,
( state ) => [ state.blocks.order ]
);

/**
* Returns the block tree represented in the block-editor store from the
* given root, consisting of stripped down block objects containing only
* their client IDs, and their inner blocks' client IDs.
*
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
*
* @return {Object[]} Client IDs of the post blocks.
*/
export const getClientIdsTree = createSelector(
getClientIdsTreeUnmemoized,
( state ) => [ state.blocks.order ]
);

function getEnabledClientIdsTreeUnmemoized( state, rootClientId ) {
const blockOrder = getBlockOrder( state, rootClientId );
const result = [];
Expand Down
53 changes: 22 additions & 31 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import {
getParentSectionBlock,
isZoomOut,
isContainerInsertableToInContentOnlyMode,
getClientIdWithClientIdsTree,
getClientIdsTree,
} from './private-selectors';

const { isContentBlock } = unlock( blocksPrivateApis );
Expand Down Expand Up @@ -225,22 +227,16 @@ export function getBlocks( state, rootClientId ) {
*
* @return {Object} Client IDs of the post blocks.
*/
export const __unstableGetClientIdWithClientIdsTree = createSelector(
( state, clientId ) => {
deprecated(
"wp.data.select( 'core/block-editor' ).__unstableGetClientIdWithClientIdsTree",
{
since: '6.3',
version: '6.5',
}
);
return {
clientId,
innerBlocks: __unstableGetClientIdsTree( state, clientId ),
};
},
( state ) => [ state.blocks.order ]
);
export function __unstableGetClientIdWithClientIdsTree( state, clientId ) {
deprecated(
"wp.data.select( 'core/block-editor' ).__unstableGetClientIdWithClientIdsTree",
{
since: '6.3',
version: '6.5',
}
);
return getClientIdWithClientIdsTree( state, clientId );
}

/**
* Returns the block tree represented in the block-editor store from the
Expand All @@ -254,21 +250,16 @@ export const __unstableGetClientIdWithClientIdsTree = createSelector(
*
* @return {Object[]} Client IDs of the post blocks.
*/
export const __unstableGetClientIdsTree = createSelector(
( state, rootClientId = '' ) => {
deprecated(
"wp.data.select( 'core/block-editor' ).__unstableGetClientIdsTree",
{
since: '6.3',
version: '6.5',
}
);
return getBlockOrder( state, rootClientId ).map( ( clientId ) =>
__unstableGetClientIdWithClientIdsTree( state, clientId )
);
},
( state ) => [ state.blocks.order ]
);
export function __unstableGetClientIdsTree( state, rootClientId ) {
deprecated(
"wp.data.select( 'core/block-editor' ).__unstableGetClientIdsTree",
{
since: '6.3',
version: '6.5',
}
);
return getClientIdsTree( state, rootClientId );
}

/**
* Returns an array containing the clientIds of all descendants of the blocks
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/store/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -5005,6 +5005,7 @@ describe( '__unstableGetClientIdsTree', () => {
},
{ clientId: 'baz', innerBlocks: [] },
] );
expect( console ).toHaveWarned();
} );

it( "should return the full content tree starting from the root, consisting of stripped down block object containing only its client ID and its inner blocks' client IDs", () => {
Expand Down
83 changes: 21 additions & 62 deletions packages/core-data/src/awareness/block-lookup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { select } from '@wordpress/data';
import { useSelect } from '@wordpress/data';
import { Y } from '@wordpress/sync';
// @ts-ignore No exported types for block editor store selectors.
import { store as blockEditorStore } from '@wordpress/block-editor';
Expand All @@ -10,17 +10,17 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
* Internal dependencies
*/
import type { AbsoluteBlockIndexPath } from '../types';
import { unlock } from '../lock-unlock';

/**
* A block as represented in the block-editor store (from `getBlocks()`).
* A block as represented in the block-editor store's client ID tree.
*
* This is a minimal interface covering only the fields used by RTC awareness.
*/
interface EditorStoreBlock {
export type EditorStoreBlock = {
clientId: string;
name: string;
innerBlocks: EditorStoreBlock[];
}
};

/**
* Find the block Y.Map that contains a nested Yjs type.
Expand Down Expand Up @@ -112,26 +112,18 @@ export function getBlockPathInYdoc(
* Navigate the block-editor store's block tree by an index path
* and return the local block's clientId.
*
* In template mode, getBlocks() returns the full template tree, but Yjs
* paths are relative to the post content. This method finds the
* core/post-content block (if present) and uses its inner blocks as the
* navigation root, so paths align with the Yjs document structure.
*
* @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
* @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
* @param blocks - The tree of block-editor store post contentblocks.
* @return The local block clientId, or null if the path is invalid.
*/
export function resolveBlockClientIdByPath(
path: AbsoluteBlockIndexPath
path: AbsoluteBlockIndexPath,
blocks: EditorStoreBlock[]
): string | null {
if ( path.length === 0 ) {
return null;
}

const { getBlocks } = select( blockEditorStore );
const postContentBlocks = getPostContentBlocks( getBlocks(), getBlocks );

let blocks = postContentBlocks;

for ( let i = 0; i < path.length; i++ ) {
const block = blocks[ path[ i ] ];
if ( ! block ) {
Expand All @@ -151,53 +143,20 @@ export function resolveBlockClientIdByPath(
* In template mode, the block tree contains template parts wrapping a
* core/post-content block. The Yjs document only stores the post content
* blocks, so we need to find the core/post-content block and use
* getBlocks(clientId) to retrieve its inner blocks from the store.
* getClientIdsTree(clientId) to retrieve its inner blocks from the store.
*
* We must use getBlocks(clientId) rather than reading .innerBlocks from
* the block object because useBlockSync() injects post content as
* controlled inner blocks — they exist in the store's block order map
* but are not populated in the .innerBlocks property of the tree
* returned by getBlocks().
* Uses the private getClientIdsTree selector which depends only on
* state.blocks.order, avoiding unnecessary re-renders when block
* attributes change (which would happen with getBlocks()).
*
* @param rootBlocks - The root-level blocks from getBlocks().
* @param getBlocks - The getBlocks selector.
* @return The blocks that correspond to the Yjs document root.
*/
function getPostContentBlocks(
rootBlocks: EditorStoreBlock[],
getBlocks: ( rootClientId?: string ) => EditorStoreBlock[]
): EditorStoreBlock[] {
const postContentBlock = findBlockByName( rootBlocks, 'core/post-content' );
if ( postContentBlock ) {
// Use getBlocks(clientId) to read controlled inner blocks from
// the store, since postContentBlock.innerBlocks is empty.
return getBlocks( postContentBlock.clientId );
}

return rootBlocks;
}

/**
* Recursively search the block tree for a block with a given name.
*
* @param blocks - The blocks to search.
* @param name - The block name to find.
* @return The first matching block, or null if not found.
*/
function findBlockByName(
blocks: EditorStoreBlock[],
name: string
): EditorStoreBlock | null {
for ( const block of blocks ) {
if ( block.name === name ) {
return block;
}
if ( block.innerBlocks?.length ) {
const found = findBlockByName( block.innerBlocks, name );
if ( found ) {
return found;
}
}
}
return null;
export function usePostContentBlocks(): EditorStoreBlock[] {
return useSelect( ( select ) => {
const { getBlocksByName, getClientIdsTree } = unlock(
select( blockEditorStore )
);
const [ postContentClientId ] = getBlocksByName( 'core/post-content' );
return getClientIdsTree( postContentClientId ?? '' );
}, [] );
}
11 changes: 8 additions & 3 deletions packages/core-data/src/awareness/post-editor-awareness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {
WPBlockSelection,
} from '../types';
import type { YBlocks } from '../utils/crdt-blocks';
import type { EditorStoreBlock } from './block-lookup';
import type {
DebugCollaboratorData,
EditorState,
Expand Down Expand Up @@ -245,10 +246,12 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
* clientIds (e.g. in "Show Template" mode where blocks are cloned).
*
* @param selection - The selection state.
* @param blocks - The tree of block-editor store post content blocks.
* @return The rich-text offset and block client ID, or nulls if not resolvable.
*/
public convertSelectionStateToAbsolute(
selection: SelectionState
selection: SelectionState,
blocks: EditorStoreBlock[]
): ResolvedSelection {
if ( selection.type === SelectionType.None ) {
return {
Expand All @@ -273,7 +276,7 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
if ( block instanceof Y.Map ) {
const path = getBlockPathInYdoc( block );
localClientId = path
? resolveBlockClientIdByPath( path )
? resolveBlockClientIdByPath( path, blocks )
: null;
}
}
Expand Down Expand Up @@ -306,7 +309,9 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {

const yType = getContainingBlockYMap( absolutePosition.type );
const path = yType ? getBlockPathInYdoc( yType ) : null;
const localClientId = path ? resolveBlockClientIdByPath( path ) : null;
const localClientId = path
? resolveBlockClientIdByPath( path, blocks )
: null;

return {
richTextOffset: htmlIndexToRichTextOffset(
Expand Down
Loading
Loading