UNPKG

@wordpress/editor

Version:
500 lines (473 loc) 16.3 kB
import { useMemo, useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; 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'; import { useViewportMatch } from '@wordpress/compose'; import { store as blocksStore } from '@wordpress/blocks'; import { privateApis, store as blockEditorStore, } from '@wordpress/block-editor'; import { privateApis as mediaEditorPrivateApis } from '@wordpress/media-editor'; import getInserterMediaCategories from '../media-categories'; import { mediaUpload } from '../../utils'; import mediaUploadOnSuccess from '../../utils/media-upload/on-success'; import { default as mediaSideload } from '../../utils/media-sideload'; import { default as mediaSideloadFromUrl } from '../../utils/media-sideload-from-url'; import { default as mediaFinalize } from '../../utils/media-finalize'; import { default as mediaDelete } from '../../utils/media-delete'; import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { useGlobalStyles } from '../global-styles'; const { store: mediaEditorStore } = unlock( mediaEditorPrivateApis ); const EMPTY_OBJECT = {}; function __experimentalReusableBlocksSelect( select ) { const { RECEIVE_INTERMEDIATE_RESULTS } = unlock( coreDataPrivateApis ); const { getEntityRecords } = select( coreStore ); return getEntityRecords( 'postType', 'wp_block', { per_page: -1, [ RECEIVE_INTERMEDIATE_RESULTS ]: true, } ); } function __experimentalUserPatternCategoriesSelect( select ) { return select( coreStore ).getUserPatternCategories(); } const BLOCK_EDITOR_SETTINGS = [ '__experimentalBlockBindingsSupportedAttributes', '__experimentalBlockDirectory', '__experimentalDiscussionSettings', '__experimentalFeatures', '__experimentalGlobalStylesBaseStyles', 'allImageSizes', 'alignWide', 'blockStatesEditingEnabled', 'blockInspectorTabs', 'maxUploadFileSize', 'allowedMimeTypes', 'bodyPlaceholder', 'canEditCSS', 'canLockBlocks', 'canUpdateBlockBindings', 'capabilities', 'clearBlockSelection', 'codeEditingEnabled', 'colors', 'disableContentOnlyForTemplateParts', 'disableContentOnlyForUnsyncedPatterns', 'disableCustomColors', 'disableCustomFontSizes', 'disableCustomSpacingSizes', 'disableCustomGradients', 'disableLayoutStyles', 'enableCustomLineHeight', 'enableCustomSpacing', 'enableCustomUnits', 'enableOpenverseMediaCategory', 'fontSizes', 'gradients', 'generateAnchors', 'onNavigateToEntityRecord', 'imageDefaultSize', 'imageDimensions', 'imageEditing', 'imageSizes', 'isPreviewMode', 'isRTL', 'locale', 'maxWidth', 'postContentAttributes', 'postsPerPage', 'readOnly', 'styles', 'titlePlaceholder', 'supportsLayout', 'widgetTypesToHideFromLegacyWidgetBlock', '__unstableHasCustomAppender', '__unstableResolvedAssets', '__unstableIsBlockBasedTheme', ]; const { globalStylesDataKey, globalStylesLinksDataKey, selectBlockPatternsKey, reusableBlocksSelectKey, userPatternCategoriesSelectKey, sectionRootClientIdKey, mediaEditKey, getMediaSelectKey, isIsolatedEditorKey, deviceTypeKey, isNavigationOverlayContextKey, isNavigationPostEditorKey, mediaUploadOnSuccessKey, mediaSideloadFromUrlKey, openMediaEditorModalKey, } = unlock( privateApis ); /** * React hook used to compute the block editor settings to use for the post editor. * * @param {Object} settings EditorProvider settings prop. * @param {string} postType Editor root level post type. * @param {string} postId Editor root level post ID. * @param {string} renderingMode Editor rendering mode. * * @return {Object} Block Editor Settings. */ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { const isLargeViewport = useViewportMatch( 'medium' ); const { allImageSizes, bigImageSizeThreshold, imageStripMeta, imageMaxBitDepth, allowRightClickOverrides, blockTypes, focusMode, hasFixedToolbar, isDistractionFree, keepCaretInsideBlock, hasUploadPermissions, hiddenBlockTypes, canUseUnfilteredHTML, userCanCreatePages, pageOnFront, pageForPosts, restBlockPatternCategories, sectionRootClientId, deviceType, isNavigationOverlayContext, isRevisionsMode, viewablePostTypeLabel, currentPostId, } = useSelect( ( select ) => { const { canUser, getRawEntityRecord, getEntityRecord, getBlockPatternCategories, getPostType, } = select( coreStore ); const { get } = select( preferencesStore ); const { getBlockTypes } = select( blocksStore ); const { getCurrentPostId, getCurrentPostType } = select( editorStore ); const { getDeviceType, isRevisionsMode: _isRevisionsMode } = unlock( select( editorStore ) ); const { getBlocksByName, getBlockAttributes } = select( blockEditorStore ); const siteSettings = canUser( 'read', { kind: 'root', name: 'site', } ) ? getEntityRecord( 'root', 'site' ) : undefined; // Fetch image sizes from REST API index for client-side media processing. const baseData = getEntityRecord( 'root', '__unstableBase' ); // The attached-images category follows the post being edited, not the // root-level entity in `postType`/`postId`. With "Show template" on, // the root becomes the template (wp_template), but media still attaches // to the page being edited. // // Guard on a truthy slug: `getPostType()` with no slug resolves the // whole `/wp/v2/types` collection rather than the single, already // fetched record for the current type. const currentPostType = getCurrentPostType(); const postTypeObject = currentPostType ? getPostType( currentPostType ) : undefined; function getSectionRootBlock() { if ( renderingMode === 'template-locked' ) { return getBlocksByName( 'core/post-content' )?.[ 0 ] ?? ''; } return ( getBlocksByName( 'core/group' ).find( ( clientId ) => getBlockAttributes( clientId )?.tagName === 'main' ) ?? '' ); } return { allImageSizes: baseData?.image_sizes, bigImageSizeThreshold: baseData?.image_size_threshold, imageStripMeta: baseData?.image_strip_meta, imageMaxBitDepth: baseData?.image_max_bit_depth, allowRightClickOverrides: get( 'core', 'allowRightClickOverrides' ), blockTypes: getBlockTypes(), canUseUnfilteredHTML: getRawEntityRecord( 'postType', postType, postId )?._links?.hasOwnProperty( 'wp:action-unfiltered-html' ), focusMode: get( 'core', 'focusMode' ), hasFixedToolbar: get( 'core', 'fixedToolbar' ) || ! isLargeViewport, hiddenBlockTypes: get( 'core', 'hiddenBlockTypes' ), isDistractionFree: get( 'core', 'distractionFree' ), keepCaretInsideBlock: get( 'core', 'keepCaretInsideBlock' ), hasUploadPermissions: canUser( 'create', { kind: 'postType', name: 'attachment', } ) ?? true, userCanCreatePages: canUser( 'create', { kind: 'postType', name: 'page', } ), pageOnFront: siteSettings?.page_on_front, pageForPosts: siteSettings?.page_for_posts, // The post type's singular name, but only for real, front-end // rendered content (`viewable`). Empty for synced patterns, // navigation and templates, which gates the attached-images // category off for them and words its copy for everything else. viewablePostTypeLabel: postTypeObject?.viewable ? postTypeObject?.labels?.singular_name : undefined, currentPostId: getCurrentPostId(), restBlockPatternCategories: getBlockPatternCategories(), sectionRootClientId: getSectionRootBlock(), deviceType: getDeviceType(), isNavigationOverlayContext: postType === 'wp_template_part' && postId ? getEntityRecord( 'postType', 'wp_template_part', postId )?.area === 'navigation-overlay' : false, isRevisionsMode: _isRevisionsMode(), }; }, [ postType, postId, isLargeViewport, renderingMode ] ); const { merged: mergedGlobalStyles } = useGlobalStyles(); const globalStylesData = mergedGlobalStyles.styles ?? EMPTY_OBJECT; const globalStylesLinksData = mergedGlobalStyles._links ?? EMPTY_OBJECT; const settingsBlockPatterns = settings.__experimentalAdditionalBlockPatterns ?? // WP 6.0 settings.__experimentalBlockPatterns; // WP 5.9 const settingsBlockPatternCategories = settings.__experimentalAdditionalBlockPatternCategories ?? // WP 6.0 settings.__experimentalBlockPatternCategories; // WP 5.9 const blockPatterns = useMemo( () => [ ...( settingsBlockPatterns || [] ) ].filter( ( { postTypes } ) => { return ( ! postTypes || ( Array.isArray( postTypes ) && postTypes.includes( postType ) ) ); } ), [ settingsBlockPatterns, postType ] ); const blockPatternCategories = useMemo( () => [ ...( settingsBlockPatternCategories || [] ), ...( restBlockPatternCategories || [] ), ].filter( ( x, index, arr ) => index === arr.findIndex( ( y ) => x.name === y.name ) ), [ settingsBlockPatternCategories, restBlockPatternCategories ] ); const { undo, setIsInserterOpened } = useDispatch( editorStore ); const { editMediaEntity } = unlock( useDispatch( coreStore ) ); const { saveEntityRecord } = useDispatch( coreStore ); const { openMediaEditorModal } = useDispatch( mediaEditorStore ); /** * Creates a Post entity. * This is utilised by the Link UI to allow for on-the-fly creation of Posts/Pages. * * @param {Object} options parameters for the post being created. These mirror those used on 3rd param of saveEntityRecord. * @return {Object} the post type object that was created. */ const createPageEntity = useCallback( ( options ) => { if ( ! userCanCreatePages ) { return Promise.reject( { message: __( 'You do not have permission to create Pages.' ), } ); } return saveEntityRecord( 'postType', 'page', options ); }, [ saveEntityRecord, userCanCreatePages ] ); const allowedBlockTypes = useMemo( () => { // Omit hidden block types if exists and non-empty. if ( hiddenBlockTypes && hiddenBlockTypes.length > 0 ) { // Defer to passed setting for `allowedBlockTypes` if provided as // anything other than `true` (where `true` is equivalent to allow // all block types). const defaultAllowedBlockTypes = true === settings.allowedBlockTypes ? blockTypes.map( ( { name } ) => name ) : settings.allowedBlockTypes || []; return defaultAllowedBlockTypes.filter( ( type ) => ! hiddenBlockTypes.includes( type ) ); } return settings.allowedBlockTypes; }, [ settings.allowedBlockTypes, hiddenBlockTypes, blockTypes ] ); // The "Attachments" media category depends on the edited post and its post // type label (which gates whether it's offered and words its copy), so the // categories are derived rather than being a static list. const inserterMediaCategories = useMemo( () => getInserterMediaCategories( currentPostId, viewablePostTypeLabel ), [ currentPostId, viewablePostTypeLabel ] ); return useMemo( () => { const blockEditorSettings = { ...Object.fromEntries( Object.entries( settings ).filter( ( [ key ] ) => BLOCK_EDITOR_SETTINGS.includes( key ) ) ), [ globalStylesDataKey ]: globalStylesData, [ globalStylesLinksDataKey ]: globalStylesLinksData, allImageSizes, bigImageSizeThreshold, imageStripMeta, imageMaxBitDepth, allowedBlockTypes, allowRightClickOverrides, focusMode, hasFixedToolbar, isDistractionFree, keepCaretInsideBlock, [ getMediaSelectKey ]: ( select, attachmentId ) => { return select( coreStore ).getEntityRecord( 'postType', 'attachment', attachmentId ); }, [ mediaEditKey ]: hasUploadPermissions ? editMediaEntity : undefined, [ openMediaEditorModalKey ]: ( { id, onUpdate, onClose } ) => openMediaEditorModal( { id, onUpdate, onClose } ), mediaUpload: hasUploadPermissions ? mediaUpload : undefined, [ mediaUploadOnSuccessKey ]: hasUploadPermissions ? mediaUploadOnSuccess : undefined, mediaSideload: hasUploadPermissions ? mediaSideload : undefined, [ mediaSideloadFromUrlKey ]: hasUploadPermissions ? mediaSideloadFromUrl : undefined, mediaFinalize: hasUploadPermissions ? mediaFinalize : undefined, mediaDelete: hasUploadPermissions ? mediaDelete : undefined, __experimentalBlockPatterns: blockPatterns, [ selectBlockPatternsKey ]: ( select ) => { const { hasFinishedResolution, getBlockPatternsForPostType } = unlock( select( coreStore ) ); const patterns = getBlockPatternsForPostType( postType ); return hasFinishedResolution( 'getBlockPatterns' ) ? patterns : undefined; }, [ reusableBlocksSelectKey ]: __experimentalReusableBlocksSelect, [ userPatternCategoriesSelectKey ]: __experimentalUserPatternCategoriesSelect, __experimentalBlockPatternCategories: blockPatternCategories, __experimentalFetchLinkSuggestions: ( search, searchOptions ) => fetchLinkSuggestions( search, searchOptions, settings ), inserterMediaCategories, __experimentalFetchRichUrlData: fetchUrlData, // Todo: This only checks the top level post, not the post within a template or any other entity that can be edited. // This might be better as a generic "canUser" selector. __experimentalCanUserUseUnfilteredHTML: canUseUnfilteredHTML, //Todo: this is only needed for native and should probably be removed. __experimentalUndo: undo, // Check whether we want all site editor frames to have outlines // including the navigation / pattern / parts editors. outlineMode: ! isDistractionFree && postType === 'wp_template', // Check these two properties: they were not present in the site editor. __experimentalCreatePageEntity: createPageEntity, __experimentalUserCanCreatePages: userCanCreatePages, pageOnFront, pageForPosts, __experimentalPreferPatternsOnRoot: postType === 'wp_template', templateLock: postType === 'wp_navigation' ? 'insert' : settings.templateLock, template: postType === 'wp_navigation' ? [ [ 'core/navigation', {}, [] ] ] : settings.template, __experimentalSetIsInserterOpened: setIsInserterOpened, [ sectionRootClientIdKey ]: sectionRootClientId, editorTool: renderingMode === 'post-only' && postType !== 'wp_template' ? 'edit' : undefined, // When editing template parts, patterns, or navigation directly, // we're in an isolated editing context (focused on that entity alone). [ isIsolatedEditorKey ]: [ 'wp_template_part', 'wp_block', 'wp_navigation', ].includes( postType ), [ isNavigationPostEditorKey ]: postType === 'wp_navigation', // When in template-locked mode (e.g., "Show Template" in the post editor), // don't treat template parts as contentOnly sections. disableContentOnlyForTemplateParts: renderingMode === 'template-locked' || settings.disableContentOnlyForTemplateParts, ...( deviceType ? { [ deviceTypeKey ]: deviceType } : {} ), [ isNavigationOverlayContextKey ]: isNavigationOverlayContext, }; if ( isRevisionsMode ) { blockEditorSettings.isPreviewMode = true; } return blockEditorSettings; }, [ isRevisionsMode, allowedBlockTypes, allowRightClickOverrides, focusMode, hasFixedToolbar, isDistractionFree, keepCaretInsideBlock, settings, hasUploadPermissions, blockPatterns, blockPatternCategories, inserterMediaCategories, canUseUnfilteredHTML, undo, createPageEntity, userCanCreatePages, pageOnFront, pageForPosts, postType, setIsInserterOpened, sectionRootClientId, globalStylesData, globalStylesLinksData, renderingMode, editMediaEntity, openMediaEditorModal, deviceType, allImageSizes, bigImageSizeThreshold, imageStripMeta, imageMaxBitDepth, isNavigationOverlayContext, ] ); } export default useBlockEditorSettings;