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

Block editor settings: delay selecting pageOnFront, pageForPosts #57290

Open
wants to merge 10 commits into
base: trunk
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import {
URL_TYPE,
} from './constants';
import { store as blockEditorStore } from '../../store';

export const handleNoop = () => Promise.resolve( [] );
import { default as settingsKeys } from '../../private-settings-keys';

export const handleDirectEntry = ( val ) => {
let type = URL_TYPE;
Expand Down Expand Up @@ -47,103 +46,65 @@ export const handleDirectEntry = ( val ) => {
] );
};

const handleEntitySearch = async (
val,
suggestionsQuery,
fetchSearchSuggestions,
withCreateSuggestion,
pageOnFront,
pageForPosts
) => {
const { isInitialSuggestions } = suggestionsQuery;

const results = await fetchSearchSuggestions( val, suggestionsQuery );

// Identify front page and update type to match.
results.map( ( result ) => {
if ( Number( result.id ) === pageOnFront ) {
result.isFrontPage = true;
return result;
} else if ( Number( result.id ) === pageForPosts ) {
result.isBlogHome = true;
return result;
}

return result;
} );

// If displaying initial suggestions just return plain results.
if ( isInitialSuggestions ) {
return results;
}

// Here we append a faux suggestion to represent a "CREATE" option. This
// is detected in the rendering of the search results and handled as a
// special case. This is currently necessary because the suggestions
// dropdown will only appear if there are valid suggestions and
// therefore unless the create option is a suggestion it will not
// display in scenarios where there are no results returned from the
// API. In addition promoting CREATE to a first class suggestion affords
// the a11y benefits afforded by `URLInput` to all suggestions (eg:
// keyboard handling, ARIA roles...etc).
//
// Note also that the value of the `title` and `url` properties must correspond
// to the text value of the `<input>`. This is because `title` is used
// when creating the suggestion. Similarly `url` is used when using keyboard to select
// the suggestion (the <form> `onSubmit` handler falls-back to `url`).
return isURLLike( val ) || ! withCreateSuggestion
? results
: results.concat( {
// the `id` prop is intentionally ommitted here because it
// is never exposed as part of the component's public API.
// see: https://github.com/WordPress/gutenberg/pull/19775#discussion_r378931316.
title: val, // Must match the existing `<input>`s text value.
url: val, // Must match the existing `<input>`s text value.
type: CREATE_TYPE,
} );
};

export default function useSearchHandler(
suggestionsQuery,
allowDirectEntry,
withCreateSuggestion
) {
const { fetchSearchSuggestions, pageOnFront, pageForPosts } = useSelect(
( select ) => {
const { getSettings } = select( blockEditorStore );

return {
pageOnFront: getSettings().pageOnFront,
pageForPosts: getSettings().pageForPosts,
fetchSearchSuggestions:
getSettings().__experimentalFetchLinkSuggestions,
};
},
[]
);

const handleNoop = useCallback( () => Promise.resolve( [] ), [] );
const directEntryHandler = allowDirectEntry
? handleDirectEntry
: handleNoop;

const useHandleEntitySearch = useSelect( ( select ) => {
const settings = select( blockEditorStore ).getSettings();
return settings[ settingsKeys.useLinkControlEntitySearch ];
}, [] );
// Note that this is meant to be a stable function and doesn't change, so it
// doesn't break the rules of hooks.
const handleEntitySearch = useHandleEntitySearch?.() || handleNoop;

return useCallback(
( val, { isInitialSuggestions } ) => {
return isURLLike( val )
? directEntryHandler( val, { isInitialSuggestions } )
: handleEntitySearch(
val,
{ ...suggestionsQuery, isInitialSuggestions },
fetchSearchSuggestions,
withCreateSuggestion,
pageOnFront,
pageForPosts
);
async ( val, { isInitialSuggestions } ) => {
if ( isURLLike( val ) ) {
return directEntryHandler( val, { isInitialSuggestions } );
}

const results = await handleEntitySearch( val, suggestionsQuery );

// If displaying initial suggestions just return plain results.
if ( isInitialSuggestions ) {
return results;
}

// Here we append a faux suggestion to represent a "CREATE" option. This
// is detected in the rendering of the search results and handled as a
// special case. This is currently necessary because the suggestions
// dropdown will only appear if there are valid suggestions and
// therefore unless the create option is a suggestion it will not
// display in scenarios where there are no results returned from the
// API. In addition promoting CREATE to a first class suggestion affords
// the a11y benefits afforded by `URLInput` to all suggestions (eg:
// keyboard handling, ARIA roles...etc).
//
// Note also that the value of the `title` and `url` properties must correspond
// to the text value of the `<input>`. This is because `title` is used
// when creating the suggestion. Similarly `url` is used when using keyboard to select
// the suggestion (the <form> `onSubmit` handler falls-back to `url`).
return ! withCreateSuggestion
? results
: results.concat( {
// the `id` prop is intentionally ommitted here because it
// is never exposed as part of the component's public API.
// see: https://github.com/WordPress/gutenberg/pull/19775#discussion_r378931316.
title: val, // Must match the existing `<input>`s text value.
url: val, // Must match the existing `<input>`s text value.
type: CREATE_TYPE,
} );
},
[
directEntryHandler,
fetchSearchSuggestions,
pageOnFront,
pageForPosts,
handleEntitySearch,
suggestionsQuery,
withCreateSuggestion,
]
Expand Down
2 changes: 2 additions & 0 deletions packages/block-editor/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { usesContextKey } from './components/rich-text/format-edit';
import { ExperimentalBlockCanvas } from './components/block-canvas';
import { getDuotoneFilter } from './components/duotone/utils';
import { default as settingsKeys } from './private-settings-keys';

/**
* Private @wordpress/block-editor APIs.
Expand Down Expand Up @@ -52,4 +53,5 @@ lock( privateApis, {
ReusableBlocksRenameHint,
useReusableBlocksRenameHint,
usesContextKey,
settingsKeys,
} );
2 changes: 2 additions & 0 deletions packages/block-editor/src/private-apis.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import * as globalStyles from './components/global-styles';
import { ExperimentalBlockEditorProvider } from './components/provider';
import { lock } from './lock-unlock';
import { default as settingsKeys } from './private-settings-keys';

/**
* Private @wordpress/block-editor APIs.
Expand All @@ -12,4 +13,5 @@ export const privateApis = {};
lock( privateApis, {
...globalStyles,
ExperimentalBlockEditorProvider,
settingsKeys,
} );
3 changes: 3 additions & 0 deletions packages/block-editor/src/private-settings-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
useLinkControlEntitySearch: Symbol( 'useLinkControlEntitySearch' ),
};
56 changes: 55 additions & 1 deletion packages/core-data/src/fetch/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,56 @@
export { default as __experimentalFetchLinkSuggestions } from './__experimental-fetch-link-suggestions';
/**
* WordPress dependencies
*/
import { useCallback } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import { store as coreStore } from '../';
import { default as fetchLinkSuggestions } from './__experimental-fetch-link-suggestions';

export const __experimentalFetchLinkSuggestions = fetchLinkSuggestions;
export { default as __experimentalFetchUrlData } from './__experimental-fetch-url-data';

export function __experimentalUseLinkControlEntitySearch() {
const settings = useSelect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be a private API instead of a prefixed one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am working on a plan to move this fetching to become a full selector in Core Data in order to take advantage of caching...etc. So making it private would be good.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #57582

( select ) => select( blockEditorStore ).getSettings(),
[]
);
// The function should either be undefined or a stable function reference
// throughout the editor lifetime, much like importing a function from a
// module.
const { pageOnFront, pageForPosts } = useSelect( ( select ) => {
const { canUser, getEntityRecord } = select( coreStore );

const siteSettings = canUser( 'read', 'settings' )
? getEntityRecord( 'root', 'site' )
: undefined;

return {
pageOnFront: siteSettings?.page_on_front,
pageForPosts: siteSettings?.page_for_posts,
};
}, [] );

return useCallback(
async ( val, suggestionsQuery ) => {
return (
await fetchLinkSuggestions( val, suggestionsQuery, settings )
).map( ( result ) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we instead access the original settings.__experimentalFetchLinkSuggestions and call that if it's defined? That might provide backwards compatibility we need.

if ( Number( result.id ) === pageOnFront ) {
result.isFrontPage = true;
return result;
} else if ( Number( result.id ) === pageForPosts ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return result;

I think we can remove these early returns

result.isBlogHome = true;
return result;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need these early return right?

Suggested change
return result;


return result;
} );
},
[ pageOnFront, pageForPosts, settings ]
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useEntityBlockEditor,
store as coreStore,
useResourcePermissions,
__experimentalUseLinkControlEntitySearch as useLinkControlEntitySearch,
} from '@wordpress/core-data';
import { useMemo } from '@wordpress/element';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
Expand All @@ -25,45 +26,37 @@ import { store as editWidgetsStore } from '../../store';
import { ALLOW_REUSABLE_BLOCKS } from '../../constants';
import { unlock } from '../../lock-unlock';

const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );
const { ExperimentalBlockEditorProvider, settingsKeys } = unlock(
blockEditorPrivateApis
);
const { PatternsMenuItems } = unlock( editPatternsPrivateApis );

export default function WidgetAreasBlockEditorProvider( {
blockEditorSettings,
children,
...props
} ) {
const mediaPermissions = useResourcePermissions( 'media' );
const isLargeViewport = useViewportMatch( 'medium' );
const {
reusableBlocks,
isFixedToolbarActive,
keepCaretInsideBlock,
pageOnFront,
pageForPosts,
} = useSelect( ( select ) => {
const { canUser, getEntityRecord, getEntityRecords } =
select( coreStore );
const siteSettings = canUser( 'read', 'settings' )
? getEntityRecord( 'root', 'site' )
: undefined;
return {
widgetAreas: select( editWidgetsStore ).getWidgetAreas(),
widgets: select( editWidgetsStore ).getWidgets(),
reusableBlocks: ALLOW_REUSABLE_BLOCKS
? getEntityRecords( 'postType', 'wp_block' )
: [],
isFixedToolbarActive: !! select( preferencesStore ).get(
'core/edit-widgets',
'fixedToolbar'
),
keepCaretInsideBlock: !! select( preferencesStore ).get(
'core/edit-widgets',
'keepCaretInsideBlock'
),
pageOnFront: siteSettings?.page_on_front,
pageForPosts: siteSettings?.page_for_posts,
};
}, [] );
const { reusableBlocks, isFixedToolbarActive, keepCaretInsideBlock } =
useSelect( ( select ) => {
const { getEntityRecords } = select( coreStore );
return {
widgetAreas: select( editWidgetsStore ).getWidgetAreas(),
widgets: select( editWidgetsStore ).getWidgets(),
reusableBlocks: ALLOW_REUSABLE_BLOCKS
? getEntityRecords( 'postType', 'wp_block' )
: [],
isFixedToolbarActive: !! select( preferencesStore ).get(
'core/edit-widgets',
'fixedToolbar'
),
keepCaretInsideBlock: !! select( preferencesStore ).get(
'core/edit-widgets',
'keepCaretInsideBlock'
),
};
}, [] );
const { setIsInserterOpened } = useDispatch( editWidgetsStore );

const settings = useMemo( () => {
Expand All @@ -85,8 +78,8 @@ export default function WidgetAreasBlockEditorProvider( {
mediaUpload: mediaUploadBlockEditor,
templateLock: 'all',
__experimentalSetIsInserterOpened: setIsInserterOpened,
pageOnFront,
pageForPosts,
[ settingsKeys.useLinkControlEntitySearch ]:
useLinkControlEntitySearch,
};
}, [
blockEditorSettings,
Expand All @@ -96,8 +89,6 @@ export default function WidgetAreasBlockEditorProvider( {
mediaPermissions.canCreate,
reusableBlocks,
setIsInserterOpened,
pageOnFront,
pageForPosts,
] );

const widgetAreaId = useLastSelectedWidgetArea();
Expand Down
Loading
Loading