diff --git a/docs/reference-guides/data/data-core-edit-post.md b/docs/reference-guides/data/data-core-edit-post.md index e39a5b483b8949..f11b3d65ab387d 100644 --- a/docs/reference-guides/data/data-core-edit-post.md +++ b/docs/reference-guides/data/data-core-edit-post.md @@ -81,6 +81,14 @@ _Returns_ - `string`: Editing mode. +### getHiddenBlockTypes + +Returns an array of blocks that are hidden. + +_Returns_ + +- `Array`: A list of the hidden block types + ### getMetaBoxesPerLocation Returns the list of all the available meta boxes for a given location. @@ -352,17 +360,12 @@ _Returns_ ### hideBlockTypes -Returns an action object used in signalling that block types by the given -name(s) should be hidden. +Update the provided block types to be hidden. _Parameters_ - _blockNames_ `string[]`: Names of block types to hide. -_Returns_ - -- `Object`: Action object. - ### initializeMetaBoxes Initializes WordPress `postboxes` script and the logic for saving meta boxes. @@ -477,17 +480,12 @@ _Returns_ ### showBlockTypes -Returns an action object used in signalling that block types by the given -name(s) should be shown. +Update the provided block types to be visible. _Parameters_ - _blockNames_ `string[]`: Names of block types to show. -_Returns_ - -- `Object`: Action object. - ### switchEditorMode Triggers an action used to switch editor mode. diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index d39b96f6b686e7..18f74434b2dca1 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -296,6 +296,54 @@ export function migrateFeaturePreferencesToPreferencesStore( } } +export function migrateIndividualPreferenceToPreferencesStore( + persistence, + sourceStoreName, + key +) { + const preferencesStoreName = 'core/preferences'; + const state = persistence.get(); + const sourcePreference = state[ sourceStoreName ]?.preferences?.[ key ]; + + // There's nothing to migrate, exit early. + if ( ! sourcePreference ) { + return; + } + + const targetPreference = + state[ preferencesStoreName ]?.preferences?.[ sourceStoreName ]?.[ + key + ]; + + // There's existing data at the target, so don't overwrite it, exit early. + if ( targetPreference ) { + return; + } + + const allPreferences = state[ preferencesStoreName ]?.preferences; + const targetPreferences = + state[ preferencesStoreName ]?.preferences?.[ sourceStoreName ]; + + persistence.set( preferencesStoreName, { + preferences: { + ...allPreferences, + [ sourceStoreName ]: { + ...targetPreferences, + [ key ]: sourcePreference, + }, + }, + } ); + + // Remove migrated feature preferences from the source. + const allSourcePreferences = state[ sourceStoreName ]?.preferences; + persistence.set( sourceStoreName, { + preferences: { + ...allSourcePreferences, + [ key ]: undefined, + }, + } ); +} + /** * Move the 'features' object in local storage from the sourceStoreName to the * interface store. @@ -358,6 +406,11 @@ persistencePlugin.__unstableMigrate = ( pluginOptions ) => { persistence, 'core/edit-post' ); + migrateIndividualPreferenceToPreferencesStore( + persistence, + 'core/edit-post', + 'hiddenBlockTypes' + ); }; export default persistencePlugin; diff --git a/packages/data/src/plugins/persistence/test/index.js b/packages/data/src/plugins/persistence/test/index.js index ee34b97fb09bf5..0f3284c5ae7720 100644 --- a/packages/data/src/plugins/persistence/test/index.js +++ b/packages/data/src/plugins/persistence/test/index.js @@ -11,6 +11,7 @@ import plugin, { withLazySameState, migrateFeaturePreferencesToInterfaceStore, migrateFeaturePreferencesToPreferencesStore, + migrateIndividualPreferenceToPreferencesStore, } from '../'; import objectStorage from '../storage/object'; import { createRegistry } from '../../../'; @@ -767,4 +768,133 @@ describe( 'migrateFeaturePreferencesToInterfaceStore', () => { } ); } ); } ); + + describe( 'migrateIndividualPreferenceToPreferencesStore', () => { + it( 'migrates an individual preference from the source to the preferences store', () => { + const persistenceInterface = createPersistenceInterface( { + storageKey: 'test-username', + } ); + + const initialState = { + preferences: { + myPreference: '123', + }, + }; + + persistenceInterface.set( 'core/test', initialState ); + + migrateIndividualPreferenceToPreferencesStore( + persistenceInterface, + 'core/test', + 'myPreference' + ); + + expect( persistenceInterface.get() ).toEqual( { + 'core/preferences': { + preferences: { + 'core/test': { + myPreference: '123', + }, + }, + }, + 'core/test': { + preferences: { + myPreference: undefined, + }, + }, + } ); + } ); + + it( 'does not overwrite other preferences in the preferences store', () => { + const persistenceInterface = createPersistenceInterface( { + storageKey: 'test-username', + } ); + + const initialState = { + preferences: { + myPreference: '123', + }, + }; + + persistenceInterface.set( 'core/test', initialState ); + persistenceInterface.set( 'core/preferences', { + preferences: { + 'core/other-store': { + preferenceA: 1, + preferenceB: 2, + }, + 'core/test': { + unrelatedPreference: 'unrelated-value', + }, + }, + } ); + + migrateIndividualPreferenceToPreferencesStore( + persistenceInterface, + 'core/test', + 'myPreference' + ); + + expect( persistenceInterface.get() ).toEqual( { + 'core/preferences': { + preferences: { + 'core/other-store': { + preferenceA: 1, + preferenceB: 2, + }, + 'core/test': { + unrelatedPreference: 'unrelated-value', + myPreference: '123', + }, + }, + }, + 'core/test': { + preferences: { + myPreference: undefined, + }, + }, + } ); + } ); + + it( 'does not migrate data if there is already a matching preference key at the target', () => { + const persistenceInterface = createPersistenceInterface( { + storageKey: 'test-username', + } ); + + persistenceInterface.set( 'core/test', { + preferences: { + myPreference: '123', + }, + } ); + + persistenceInterface.set( 'core/preferences', { + preferences: { + 'core/test': { + myPreference: 'already-set', + }, + }, + } ); + + migrateIndividualPreferenceToPreferencesStore( + persistenceInterface, + 'core/test', + 'myPreference' + ); + + expect( persistenceInterface.get() ).toEqual( { + 'core/preferences': { + preferences: { + 'core/test': { + myPreference: 'already-set', + }, + }, + }, + 'core/test': { + preferences: { + myPreference: '123', + }, + }, + } ); + } ); + } ); } ); diff --git a/packages/edit-post/src/components/block-manager/category.js b/packages/edit-post/src/components/block-manager/category.js index b6d75552491f12..2a13a80d6b9aaa 100644 --- a/packages/edit-post/src/components/block-manager/category.js +++ b/packages/edit-post/src/components/block-manager/category.js @@ -23,11 +23,11 @@ function BlockManagerCategory( { title, blockTypes } ) { const { defaultAllowedBlockTypes, hiddenBlockTypes } = useSelect( ( select ) => { const { getEditorSettings } = select( editorStore ); - const { getPreference } = select( editPostStore ); + const { getHiddenBlockTypes } = select( editPostStore ); return { defaultAllowedBlockTypes: getEditorSettings() .defaultAllowedBlockTypes, - hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), + hiddenBlockTypes: getHiddenBlockTypes(), }; }, [] diff --git a/packages/edit-post/src/components/block-manager/index.js b/packages/edit-post/src/components/block-manager/index.js index c334078c1fcfdb..e2d455399364ea 100644 --- a/packages/edit-post/src/components/block-manager/index.js +++ b/packages/edit-post/src/components/block-manager/index.js @@ -116,8 +116,8 @@ export default withSelect( ( select ) => { hasBlockSupport, isMatchingSearchTerm, } = select( blocksStore ); - const { getPreference } = select( editPostStore ); - const hiddenBlockTypes = getPreference( 'hiddenBlockTypes' ); + const { getHiddenBlockTypes } = select( editPostStore ); + const hiddenBlockTypes = getHiddenBlockTypes(); const numberOfHiddenBlocks = isArray( hiddenBlockTypes ) && hiddenBlockTypes.length; diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index ddeb0bf8e0be13..f993866f0e4df1 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -55,6 +55,7 @@ function Editor( { __experimentalGetPreviewDeviceType, isEditingTemplate, getEditedPostTemplate, + getHiddenBlockTypes, } = select( editPostStore ); const { getEntityRecord, getPostType, getEntityRecords } = select( coreStore @@ -89,7 +90,7 @@ function Editor( { preferredStyleVariations: getPreference( 'preferredStyleVariations' ), - hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), + hiddenBlockTypes: getHiddenBlockTypes(), blockTypes: getBlockTypes(), __experimentalLocalAutosaveInterval: getPreference( 'localAutosaveInterval' diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index 8af03621a7237b..cf4eaa53389650 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -187,8 +187,8 @@ export default compose( [ const { isFeatureActive, getEditorMode, - getPreference, __experimentalGetPreviewDeviceType, + getHiddenBlockTypes, } = select( editPostStore ); const { getBlockTypes } = select( blocksStore ); @@ -198,7 +198,7 @@ export default compose( [ __experimentalGetPreviewDeviceType() !== 'Desktop', focusMode: isFeatureActive( 'focusMode' ), mode: getEditorMode(), - hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), + hiddenBlockTypes: getHiddenBlockTypes(), blockTypes: getBlockTypes(), }; } ), diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index effcf8b9adce2b..62338a2906b843 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -108,11 +108,12 @@ export function initializeEditor( dispatch( preferencesStore ).setDefaults( 'core/edit-post', { fixedToolbar: false, - welcomeGuide: true, fullscreenMode: true, + hiddenBlockTypes: [], + showBlockBreadcrumbs: true, showIconLabels: false, themeStyles: true, - showBlockBreadcrumbs: true, + welcomeGuide: true, welcomeGuideTemplate: true, } ); diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 71fdbad8dc8576..1db042d2ac1ef5 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, reduce } from 'lodash'; +import { castArray, reduce, without } from 'lodash'; /** * WordPress dependencies @@ -188,21 +188,6 @@ export const togglePinnedPluginItem = ( pluginName ) => ( { registry } ) => { [ isPinned ? 'unpinItem' : 'pinItem' ]( 'core/edit-post', pluginName ); }; -/** - * Returns an action object used in signalling that block types by the given - * name(s) should be hidden. - * - * @param {string[]} blockNames Names of block types to hide. - * - * @return {Object} Action object. - */ -export function hideBlockTypes( blockNames ) { - return { - type: 'HIDE_BLOCK_TYPES', - blockNames: castArray( blockNames ), - }; -} - /** * Returns an action object used in signaling that a style should be auto-applied when a block is created. * @@ -234,19 +219,46 @@ export function __experimentalUpdateLocalAutosaveInterval( interval ) { } /** - * Returns an action object used in signalling that block types by the given - * name(s) should be shown. + * Update the provided block types to be visible. * * @param {string[]} blockNames Names of block types to show. + */ +export const showBlockTypes = ( blockNames ) => ( { registry } ) => { + const existingBlockNames = + registry + .select( preferencesStore ) + .get( 'core/edit-post', 'hiddenBlockTypes' ) ?? []; + + const newBlockNames = without( + existingBlockNames, + ...castArray( blockNames ) + ); + + registry + .dispatch( preferencesStore ) + .set( 'core/edit-post', 'hiddenBlockTypes', newBlockNames ); +}; + +/** + * Update the provided block types to be hidden. * - * @return {Object} Action object. + * @param {string[]} blockNames Names of block types to hide. */ -export function showBlockTypes( blockNames ) { - return { - type: 'SHOW_BLOCK_TYPES', - blockNames: castArray( blockNames ), - }; -} +export const hideBlockTypes = ( blockNames ) => ( { registry } ) => { + const existingBlockNames = + registry + .select( preferencesStore ) + .get( 'core/edit-post', 'hiddenBlockTypes' ) ?? []; + + const mergedBlockNames = new Set( [ + ...existingBlockNames, + ...castArray( blockNames ), + ] ); + + registry + .dispatch( preferencesStore ) + .set( 'core/edit-post', 'hiddenBlockTypes', [ ...mergedBlockNames ] ); +}; /** * Returns an action object used in signaling diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index d3eaa38fac7c06..846f6788273ce5 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { flow, get, includes, omit, union, without } from 'lodash'; +import { flow, get, includes, omit } from 'lodash'; /** * WordPress dependencies @@ -85,17 +85,6 @@ export const preferences = flow( [ return state; }, - hiddenBlockTypes( state, action ) { - switch ( action.type ) { - case 'SHOW_BLOCK_TYPES': - return without( state, ...action.blockNames ); - - case 'HIDE_BLOCK_TYPES': - return union( state, action.blockNames ); - } - - return state; - }, preferredStyleVariations( state, action ) { switch ( action.type ) { case 'UPDATE_PREFERRED_STYLE_VARIATIONS': { diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index a9f6aacb97fcc7..61f57a7f798dce 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -12,6 +12,9 @@ import { store as interfaceStore } from '@wordpress/interface'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as coreStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; + +const EMPTY_ARRAY = []; + /** * Returns the current editing mode. * @@ -86,6 +89,10 @@ export const getActiveGeneralSidebarName = createRegistrySelector( } ); +// The current list of preference keys that have been migrated to the +// preferences package. +const MIGRATED_KEYS = [ 'hiddenBlockTypes' ]; + /** * Returns the preferences (these preferences are persisted locally). * @@ -93,9 +100,34 @@ export const getActiveGeneralSidebarName = createRegistrySelector( * * @return {Object} Preferences Object. */ -export function getPreferences( state ) { - return state.preferences; -} +export const getPreferences = createRegistrySelector( + ( select ) => ( state ) => { + const editPostPreferences = state.preferences; + + // Some preferences now exist in the preferences store. + // Fetch them so that they can be merged into the post + // editor preferences. + const preferenceStorePreferences = MIGRATED_KEYS.reduce( + ( accumulatedPrefs, preferenceKey ) => { + const value = select( preferencesStore ).get( + 'core/edit-post', + preferenceKey + ); + + return { + ...accumulatedPrefs, + [ preferenceKey ]: value, + }; + }, + {} + ); + + return { + ...editPostPreferences, + ...preferenceStorePreferences, + }; + } +); /** * @@ -106,11 +138,29 @@ export function getPreferences( state ) { * @return {*} Preference Value. */ export function getPreference( state, preferenceKey, defaultValue ) { - const preferences = getPreferences( state ); + // Avoid using the `getPreferences` registry selector where possible. + const isMigratedKey = MIGRATED_KEYS.includes( preferenceKey ); + const preferences = isMigratedKey + ? getPreferences( state ) + : state.preferences; const value = preferences[ preferenceKey ]; return value === undefined ? defaultValue : value; } +/** + * Returns an array of blocks that are hidden. + * + * @return {Array} A list of the hidden block types + */ +export const getHiddenBlockTypes = createRegistrySelector( ( select ) => () => { + return ( + select( preferencesStore ).get( + 'core/edit-post', + 'hiddenBlockTypes' + ) ?? EMPTY_ARRAY + ); +} ); + /** * Returns true if the publish sidebar is opened. * diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index 82e750d76b8c8d..c1b630dd3e0440 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -34,6 +34,7 @@ describe( 'actions', () => { beforeEach( () => { registry = createRegistryWithStores(); } ); + it( 'openGeneralSidebar/closeGeneralSidebar', () => { registry.dispatch( editPostStore ).openGeneralSidebar( 'test/sidebar' ); expect( @@ -51,6 +52,7 @@ describe( 'actions', () => { .getActiveComplementaryArea( 'core/edit-post' ) ).toBeNull(); } ); + it( 'toggleFeature', () => { registry.dispatch( editPostStore ).toggleFeature( 'welcomeGuide' ); expect( @@ -66,6 +68,7 @@ describe( 'actions', () => { .get( editPostStore.name, 'welcomeGuide' ) ).toBe( false ); } ); + describe( 'switchEditorMode', () => { it( 'to visual', () => { registry.dispatch( editPostStore ).switchEditorMode( 'visual' ); @@ -73,6 +76,7 @@ describe( 'actions', () => { 'visual' ); } ); + it( 'to text', () => { // Add a selected client id and make sure it's there. const clientId = 'clientId_1'; @@ -87,6 +91,7 @@ describe( 'actions', () => { ).toBeNull(); } ); } ); + it( 'togglePinnedPluginItem', () => { registry.dispatch( editPostStore ).togglePinnedPluginItem( 'rigatoni' ); // Sidebars are pinned by default. @@ -103,6 +108,7 @@ describe( 'actions', () => { .isItemPinned( editPostStore.name, 'rigatoni' ) ).toBe( true ); } ); + describe( '__unstableSwitchToTemplateMode', () => { it( 'welcome guide is active', () => { // Activate `welcomeGuideTemplate` feature. @@ -116,6 +122,7 @@ describe( 'actions', () => { const notices = registry.select( noticesStore ).getNotices(); expect( notices ).toHaveLength( 0 ); } ); + it( 'welcome guide is inactive', () => { expect( registry.select( editPostStore ).isEditingTemplate() @@ -129,4 +136,158 @@ describe( 'actions', () => { expect( notices[ 0 ].content ).toMatch( 'template' ); } ); } ); + + describe( 'hideBlockTypes', () => { + it( 'adds the hidden block type to the preferences', () => { + registry + .dispatch( editPostStore ) + .hideBlockTypes( [ 'core/quote', 'core/table' ] ); + + const expected = [ 'core/quote', 'core/table' ]; + + // TODO - remove once `getPreference` is deprecated. + expect( + registry + .select( editPostStore ) + .getPreference( 'hiddenBlockTypes' ) + ).toEqual( expected ); + + expect( + registry.select( editPostStore ).getHiddenBlockTypes() + ).toEqual( expected ); + } ); + } ); + + describe( 'showBlockTypes', () => { + it( 'removes the hidden block type from the preferences', () => { + registry + .dispatch( editPostStore ) + .hideBlockTypes( [ 'core/quote', 'core/table' ] ); + + const expectedA = [ 'core/quote', 'core/table' ]; + + // TODO - remove once `getPreference` is deprecated. + expect( + registry + .select( editPostStore ) + .getPreference( 'hiddenBlockTypes' ) + ).toEqual( expectedA ); + + expect( + registry.select( editPostStore ).getHiddenBlockTypes() + ).toEqual( expectedA ); + + registry + .dispatch( editPostStore ) + .showBlockTypes( [ 'core/table' ] ); + + const expectedB = [ 'core/quote' ]; + + // TODO - remove once `getPreference` is deprecated. + expect( + registry + .select( editPostStore ) + .getPreference( 'hiddenBlockTypes' ) + ).toEqual( expectedB ); + + expect( + registry.select( editPostStore ).getHiddenBlockTypes() + ).toEqual( expectedB ); + } ); + } ); + + describe( '__experimentalUpdateLocalAutosaveInterval', () => { + it( 'sets the local autosave interval', () => { + registry + .dispatch( editPostStore ) + .__experimentalUpdateLocalAutosaveInterval( 42 ); + + // TODO - remove once `getPreference` is deprecated. + expect( + registry + .select( editPostStore ) + .getPreference( 'localAutosaveInterval' ) + ).toBe( 42 ); + } ); + } ); + + describe( 'toggleEditorPanelEnabled', () => { + it( 'toggles panels to be enabled and not enabled', () => { + const defaultState = { + 'post-status': { + opened: true, + }, + }; + + // This will switch it off, since the default is on. + registry + .dispatch( editPostStore ) + .toggleEditorPanelEnabled( 'control-panel' ); + + // TODO - remove once `getPreference` is deprecated. + expect( + registry.select( editPostStore ).getPreference( 'panels' ) + ).toEqual( { + ...defaultState, + 'control-panel': { + enabled: false, + }, + } ); + + // Switch it on again. + registry + .dispatch( editPostStore ) + .toggleEditorPanelEnabled( 'control-panel' ); + + // TODO - remove once `getPreference` is deprecated. + expect( + registry.select( editPostStore ).getPreference( 'panels' ) + ).toEqual( { + ...defaultState, + 'control-panel': { + enabled: true, + }, + } ); + } ); + } ); + + describe( 'toggleEditorPanelOpened', () => { + it( 'toggles panels open and closed', () => { + const defaultState = { + 'post-status': { + opened: true, + }, + }; + + // This will open it, since the default is closed. + registry + .dispatch( editPostStore ) + .toggleEditorPanelOpened( 'control-panel' ); + + // TODO - remove once `getPreference` is deprecated. + expect( + registry.select( editPostStore ).getPreference( 'panels' ) + ).toEqual( { + ...defaultState, + 'control-panel': { + opened: true, + }, + } ); + + // Close it. + registry + .dispatch( editPostStore ) + .toggleEditorPanelOpened( 'control-panel' ); + + // TODO - remove once `getPreference` is deprecated. + expect( + registry.select( editPostStore ).getPreference( 'panels' ) + ).toEqual( { + ...defaultState, + 'control-panel': { + opened: false, + }, + } ); + } ); + } ); } ); diff --git a/packages/edit-post/src/store/test/reducer.js b/packages/edit-post/src/store/test/reducer.js index 67b097e27a8dd0..f469e1f088a36f 100644 --- a/packages/edit-post/src/store/test/reducer.js +++ b/packages/edit-post/src/store/test/reducer.js @@ -151,34 +151,6 @@ describe( 'state', () => { expect( state.editorMode ).toBe( 'text' ); } ); - - describe( 'hiddenBlockTypes', () => { - it( 'concatenates unique names on disable', () => { - const original = deepFreeze( { - hiddenBlockTypes: [ 'a', 'b' ], - } ); - - const state = preferences( original, { - type: 'HIDE_BLOCK_TYPES', - blockNames: [ 'b', 'c' ], - } ); - - expect( state.hiddenBlockTypes ).toEqual( [ 'a', 'b', 'c' ] ); - } ); - - it( 'omits present names by enable', () => { - const original = deepFreeze( { - hiddenBlockTypes: [ 'a', 'b' ], - } ); - - const state = preferences( original, { - type: 'SHOW_BLOCK_TYPES', - blockNames: [ 'b', 'c' ], - } ); - - expect( state.hiddenBlockTypes ).toEqual( [ 'a' ] ); - } ); - } ); } ); describe( 'activeModal', () => {