@wordpress/preferences-persistence 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE.md +788 -0
- package/README.md +59 -0
- package/build/create/debounce-async.js +80 -0
- package/build/create/debounce-async.js.map +1 -0
- package/build/create/index.js +115 -0
- package/build/create/index.js.map +1 -0
- package/build/index.js +61 -0
- package/build/index.js.map +1 -0
- package/build/migrations/legacy-local-storage-data/convert-edit-post-panels.js +60 -0
- package/build/migrations/legacy-local-storage-data/convert-edit-post-panels.js.map +1 -0
- package/build/migrations/legacy-local-storage-data/index.js +111 -0
- package/build/migrations/legacy-local-storage-data/index.js.map +1 -0
- package/build/migrations/legacy-local-storage-data/move-feature-preferences.js +135 -0
- package/build/migrations/legacy-local-storage-data/move-feature-preferences.js.map +1 -0
- package/build/migrations/legacy-local-storage-data/move-individual-preference.js +91 -0
- package/build/migrations/legacy-local-storage-data/move-individual-preference.js.map +1 -0
- package/build/migrations/legacy-local-storage-data/move-interface-enable-items.js +114 -0
- package/build/migrations/legacy-local-storage-data/move-interface-enable-items.js.map +1 -0
- package/build/migrations/legacy-local-storage-data/move-third-party-feature-preferences.js +99 -0
- package/build/migrations/legacy-local-storage-data/move-third-party-feature-preferences.js.map +1 -0
- package/build-module/create/debounce-async.js +73 -0
- package/build-module/create/debounce-async.js.map +1 -0
- package/build-module/create/index.js +104 -0
- package/build-module/create/index.js.map +1 -0
- package/build-module/index.js +45 -0
- package/build-module/index.js.map +1 -0
- package/build-module/migrations/legacy-local-storage-data/convert-edit-post-panels.js +53 -0
- package/build-module/migrations/legacy-local-storage-data/convert-edit-post-panels.js.map +1 -0
- package/build-module/migrations/legacy-local-storage-data/index.js +95 -0
- package/build-module/migrations/legacy-local-storage-data/index.js.map +1 -0
- package/build-module/migrations/legacy-local-storage-data/move-feature-preferences.js +128 -0
- package/build-module/migrations/legacy-local-storage-data/move-feature-preferences.js.map +1 -0
- package/build-module/migrations/legacy-local-storage-data/move-individual-preference.js +84 -0
- package/build-module/migrations/legacy-local-storage-data/move-individual-preference.js.map +1 -0
- package/build-module/migrations/legacy-local-storage-data/move-interface-enable-items.js +107 -0
- package/build-module/migrations/legacy-local-storage-data/move-interface-enable-items.js.map +1 -0
- package/build-module/migrations/legacy-local-storage-data/move-third-party-feature-preferences.js +92 -0
- package/build-module/migrations/legacy-local-storage-data/move-third-party-feature-preferences.js.map +1 -0
- package/package.json +37 -0
- package/src/create/debounce-async.js +75 -0
- package/src/create/index.js +112 -0
- package/src/create/test/debounce-async.js +129 -0
- package/src/create/test/index.js +178 -0
- package/src/index.js +49 -0
- package/src/migrations/legacy-local-storage-data/README.md +42 -0
- package/src/migrations/legacy-local-storage-data/convert-edit-post-panels.js +50 -0
- package/src/migrations/legacy-local-storage-data/index.js +102 -0
- package/src/migrations/legacy-local-storage-data/move-feature-preferences.js +135 -0
- package/src/migrations/legacy-local-storage-data/move-individual-preference.js +90 -0
- package/src/migrations/legacy-local-storage-data/move-interface-enable-items.js +120 -0
- package/src/migrations/legacy-local-storage-data/move-third-party-feature-preferences.js +98 -0
- package/src/migrations/legacy-local-storage-data/test/convert-edit-post-panels.js +47 -0
- package/src/migrations/legacy-local-storage-data/test/index.js +229 -0
- package/src/migrations/legacy-local-storage-data/test/move-feature-preferences.js +260 -0
- package/src/migrations/legacy-local-storage-data/test/move-individual-preference.js +188 -0
- package/src/migrations/legacy-local-storage-data/test/move-interface-enable-items.js +118 -0
- package/src/migrations/legacy-local-storage-data/test/move-third-party-feature-preferences.js +107 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
/**
|
2
|
+
* WordPress dependencies
|
3
|
+
*/
|
4
|
+
import apiFetch from '@wordpress/api-fetch';
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Internal dependencies
|
8
|
+
*/
|
9
|
+
import create from '..';
|
10
|
+
|
11
|
+
jest.mock( '@wordpress/api-fetch' );
|
12
|
+
|
13
|
+
describe( 'create', () => {
|
14
|
+
afterEach( () => {
|
15
|
+
apiFetch.mockReset();
|
16
|
+
} );
|
17
|
+
|
18
|
+
describe( 'set', () => {
|
19
|
+
it( 'stores backup restoration data in localStorage', () => {
|
20
|
+
apiFetch.mockResolvedValueOnce();
|
21
|
+
const spy = jest.spyOn( global.Storage.prototype, 'setItem' );
|
22
|
+
|
23
|
+
const localStorageRestoreKey = 'test';
|
24
|
+
const { set } = create( { localStorageRestoreKey } );
|
25
|
+
|
26
|
+
const data = { test: 1 };
|
27
|
+
set( data );
|
28
|
+
|
29
|
+
expect( spy ).toHaveBeenCalledWith(
|
30
|
+
localStorageRestoreKey,
|
31
|
+
expect.any( String )
|
32
|
+
);
|
33
|
+
|
34
|
+
// The second param of the call to `setItem` has been JSON.stringified.
|
35
|
+
// Parse it to check it contains the data.
|
36
|
+
const setItemDataParm = spy.mock.calls[ 0 ][ 1 ];
|
37
|
+
expect( JSON.parse( setItemDataParm ) ).toEqual(
|
38
|
+
expect.objectContaining( data )
|
39
|
+
);
|
40
|
+
} );
|
41
|
+
|
42
|
+
it( 'sends data to the `users/me` endpoint', () => {
|
43
|
+
apiFetch.mockResolvedValueOnce();
|
44
|
+
|
45
|
+
const { set } = create();
|
46
|
+
|
47
|
+
const data = { test: 1 };
|
48
|
+
set( data );
|
49
|
+
|
50
|
+
expect( apiFetch ).toHaveBeenCalledWith( {
|
51
|
+
path: '/wp/v2/users/me',
|
52
|
+
method: 'PUT',
|
53
|
+
keepalive: true,
|
54
|
+
data: {
|
55
|
+
meta: {
|
56
|
+
persisted_preferences: expect.objectContaining( data ),
|
57
|
+
},
|
58
|
+
},
|
59
|
+
} );
|
60
|
+
} );
|
61
|
+
} );
|
62
|
+
|
63
|
+
describe( 'get', () => {
|
64
|
+
it( 'avoids using the REST API or local storage when data is preloaded', async () => {
|
65
|
+
const getItemSpy = jest.spyOn(
|
66
|
+
global.Storage.prototype,
|
67
|
+
'getItem'
|
68
|
+
);
|
69
|
+
|
70
|
+
const preloadedData = { preloaded: true };
|
71
|
+
const { get } = create( { preloadedData } );
|
72
|
+
expect( await get() ).toBe( preloadedData );
|
73
|
+
expect( getItemSpy ).not.toHaveBeenCalled();
|
74
|
+
expect( apiFetch ).not.toHaveBeenCalled();
|
75
|
+
} );
|
76
|
+
|
77
|
+
it( 'returns from a local cache once `set` has been called', async () => {
|
78
|
+
const getItemSpy = jest.spyOn(
|
79
|
+
global.Storage.prototype,
|
80
|
+
'getItem'
|
81
|
+
);
|
82
|
+
apiFetch.mockResolvedValueOnce();
|
83
|
+
|
84
|
+
const data = { cached: true };
|
85
|
+
const { get, set } = create();
|
86
|
+
|
87
|
+
// apiFetch was called as a result of calling `set`.
|
88
|
+
set( data );
|
89
|
+
expect( apiFetch ).toHaveBeenCalled();
|
90
|
+
apiFetch.mockClear();
|
91
|
+
|
92
|
+
// Neither localStorage.getItem or apiFetch are called as a result
|
93
|
+
// of the call to `get`. A local cache is used.
|
94
|
+
expect( await get() ).toEqual( expect.objectContaining( data ) );
|
95
|
+
expect( getItemSpy ).not.toHaveBeenCalled();
|
96
|
+
expect( apiFetch ).not.toHaveBeenCalled();
|
97
|
+
} );
|
98
|
+
|
99
|
+
it( 'returns data from the users/me endpoint if there is no data in localStorage', async () => {
|
100
|
+
const data = {
|
101
|
+
__timestamp: 0,
|
102
|
+
test: 2,
|
103
|
+
};
|
104
|
+
apiFetch.mockResolvedValueOnce( {
|
105
|
+
meta: { persisted_preferences: data },
|
106
|
+
} );
|
107
|
+
|
108
|
+
jest.spyOn(
|
109
|
+
global.Storage.prototype,
|
110
|
+
'getItem'
|
111
|
+
).mockReturnValueOnce( 'null' );
|
112
|
+
|
113
|
+
const { get } = create();
|
114
|
+
expect( await get() ).toEqual( data );
|
115
|
+
} );
|
116
|
+
|
117
|
+
it( 'returns data from the REST API if it has a more recent modified date than localStorage', async () => {
|
118
|
+
const data = {
|
119
|
+
_modified: '2022-04-22T00:00:00.000Z',
|
120
|
+
test: 'api',
|
121
|
+
};
|
122
|
+
apiFetch.mockResolvedValueOnce( {
|
123
|
+
meta: { persisted_preferences: data },
|
124
|
+
} );
|
125
|
+
|
126
|
+
jest.spyOn(
|
127
|
+
global.Storage.prototype,
|
128
|
+
'getItem'
|
129
|
+
).mockReturnValueOnce(
|
130
|
+
JSON.stringify( {
|
131
|
+
_modified: '2022-04-21T00:00:00.000Z',
|
132
|
+
test: 'localStorage',
|
133
|
+
} )
|
134
|
+
);
|
135
|
+
|
136
|
+
const { get } = create();
|
137
|
+
expect( await get() ).toEqual( data );
|
138
|
+
} );
|
139
|
+
|
140
|
+
it( 'returns data from localStorage if it has a more recent modified date than data from the REST API', async () => {
|
141
|
+
apiFetch.mockResolvedValueOnce( {
|
142
|
+
meta: {
|
143
|
+
persisted_preferences: {
|
144
|
+
_modified: '2022-04-21T00:00:00.000Z',
|
145
|
+
test: 'api',
|
146
|
+
},
|
147
|
+
},
|
148
|
+
} );
|
149
|
+
|
150
|
+
const data = {
|
151
|
+
_modified: '2022-04-22T00:00:00.000Z',
|
152
|
+
test: 'localStorage',
|
153
|
+
};
|
154
|
+
jest.spyOn(
|
155
|
+
global.Storage.prototype,
|
156
|
+
'getItem'
|
157
|
+
).mockReturnValueOnce( JSON.stringify( data ) );
|
158
|
+
|
159
|
+
const { get } = create();
|
160
|
+
expect( await get() ).toEqual( data );
|
161
|
+
} );
|
162
|
+
|
163
|
+
it( 'returns an empty object if neither local storage or the REST API return any data', async () => {
|
164
|
+
apiFetch.mockResolvedValueOnce( {
|
165
|
+
meta: {
|
166
|
+
persisted_preferences: null,
|
167
|
+
},
|
168
|
+
} );
|
169
|
+
jest.spyOn(
|
170
|
+
global.Storage.prototype,
|
171
|
+
'getItem'
|
172
|
+
).mockReturnValueOnce( 'null' );
|
173
|
+
|
174
|
+
const { get } = create();
|
175
|
+
expect( await get() ).toEqual( {} );
|
176
|
+
} );
|
177
|
+
} );
|
178
|
+
} );
|
package/src/index.js
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
/**
|
2
|
+
* Internal dependencies
|
3
|
+
*/
|
4
|
+
import create from './create';
|
5
|
+
import convertLegacyLocalStorageData from './migrations/legacy-local-storage-data';
|
6
|
+
|
7
|
+
export { create };
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Creates the persistence layer with preloaded data.
|
11
|
+
*
|
12
|
+
* It prioritizes any data from the server, but falls back first to localStorage
|
13
|
+
* restore data, and then to any legacy data.
|
14
|
+
*
|
15
|
+
* This function is used internally by WordPress in an inline script, so
|
16
|
+
* prefixed with `__unstable`.
|
17
|
+
*
|
18
|
+
* @param {Object} serverData Preferences data preloaded from the server.
|
19
|
+
* @param {string} userId The user id.
|
20
|
+
*
|
21
|
+
* @return {Object} The persistence layer initialized with the preloaded data.
|
22
|
+
*/
|
23
|
+
export function __unstableCreatePersistenceLayer( serverData, userId ) {
|
24
|
+
const localStorageRestoreKey = `WP_PREFERENCES_USER_${ userId }`;
|
25
|
+
const localData = JSON.parse(
|
26
|
+
window.localStorage.getItem( localStorageRestoreKey )
|
27
|
+
);
|
28
|
+
|
29
|
+
// Date parse returns NaN for invalid input. Coerce anything invalid
|
30
|
+
// into a conveniently comparable zero.
|
31
|
+
const serverModified =
|
32
|
+
Date.parse( serverData && serverData._modified ) || 0;
|
33
|
+
const localModified = Date.parse( localData && localData._modified ) || 0;
|
34
|
+
|
35
|
+
let preloadedData;
|
36
|
+
if ( serverData && serverModified >= localModified ) {
|
37
|
+
preloadedData = serverData;
|
38
|
+
} else if ( localData ) {
|
39
|
+
preloadedData = localData;
|
40
|
+
} else {
|
41
|
+
// Check if there is data in the legacy format from the old persistence system.
|
42
|
+
preloadedData = convertLegacyLocalStorageData( userId );
|
43
|
+
}
|
44
|
+
|
45
|
+
return create( {
|
46
|
+
preloadedData,
|
47
|
+
localStorageRestoreKey,
|
48
|
+
} );
|
49
|
+
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Legacy local storage migrations
|
2
|
+
|
3
|
+
This folder contains all the migration code for converting from the old `data` package persistence system.
|
4
|
+
|
5
|
+
## History
|
6
|
+
|
7
|
+
Previously, some packages could configure a store to persist particular parts of state. In this example, the post editor is configured to persist the state for its `preferences` reducer to local storage:
|
8
|
+
```js
|
9
|
+
registerStore(
|
10
|
+
'core/edit-post', {
|
11
|
+
selectors,
|
12
|
+
actions,
|
13
|
+
reducer,
|
14
|
+
persist: [ 'preferences' ],
|
15
|
+
},
|
16
|
+
);
|
17
|
+
```
|
18
|
+
|
19
|
+
This would result in local storage data being saved in this format:
|
20
|
+
```json
|
21
|
+
{
|
22
|
+
"core/edit-post": {
|
23
|
+
"preferences": {
|
24
|
+
// ... preferences state from the post editor.
|
25
|
+
}
|
26
|
+
},
|
27
|
+
// ... other persisted state from other editors.
|
28
|
+
}
|
29
|
+
```
|
30
|
+
|
31
|
+
And when an editor was reloaded, this would become the initial store state.
|
32
|
+
|
33
|
+
The preferences package was later introduced, and this became a centralized place for managing and persisting preferences for other packages. The job of these migration functions is to migrate data from the old persistence system to the new format for the preferences store:
|
34
|
+
```json
|
35
|
+
{
|
36
|
+
"preferences": {
|
37
|
+
"core/edit-post": {
|
38
|
+
// ... preferences for the post editor.
|
39
|
+
}
|
40
|
+
// ... preferences for other editors
|
41
|
+
}
|
42
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
/**
|
2
|
+
* Convert the post editor's panels state from:
|
3
|
+
* ```
|
4
|
+
* {
|
5
|
+
* panels: {
|
6
|
+
* tags: {
|
7
|
+
* enabled: true,
|
8
|
+
* opened: true,
|
9
|
+
* },
|
10
|
+
* permalinks: {
|
11
|
+
* enabled: false,
|
12
|
+
* opened: false,
|
13
|
+
* },
|
14
|
+
* },
|
15
|
+
* }
|
16
|
+
* ```
|
17
|
+
*
|
18
|
+
* to a new, more concise data structure:
|
19
|
+
* {
|
20
|
+
* inactivePanels: [
|
21
|
+
* 'permalinks',
|
22
|
+
* ],
|
23
|
+
* openPanels: [
|
24
|
+
* 'tags',
|
25
|
+
* ],
|
26
|
+
* }
|
27
|
+
*
|
28
|
+
* @param {Object} preferences A preferences object.
|
29
|
+
*
|
30
|
+
* @return {Object} The converted data.
|
31
|
+
*/
|
32
|
+
export default function convertEditPostPanels( preferences ) {
|
33
|
+
const panels = preferences?.panels ?? {};
|
34
|
+
return Object.keys( panels ).reduce(
|
35
|
+
( convertedData, panelName ) => {
|
36
|
+
const panel = panels[ panelName ];
|
37
|
+
|
38
|
+
if ( panel?.enabled === false ) {
|
39
|
+
convertedData.inactivePanels.push( panelName );
|
40
|
+
}
|
41
|
+
|
42
|
+
if ( panel?.opened === true ) {
|
43
|
+
convertedData.openPanels.push( panelName );
|
44
|
+
}
|
45
|
+
|
46
|
+
return convertedData;
|
47
|
+
},
|
48
|
+
{ inactivePanels: [], openPanels: [] }
|
49
|
+
);
|
50
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
/**
|
2
|
+
* Internal dependencies
|
3
|
+
*/
|
4
|
+
import moveFeaturePreferences from './move-feature-preferences';
|
5
|
+
import moveThirdPartyFeaturePreferences from './move-third-party-feature-preferences';
|
6
|
+
import moveIndividualPreference from './move-individual-preference';
|
7
|
+
import moveInterfaceEnableItems from './move-interface-enable-items';
|
8
|
+
import convertEditPostPanels from './convert-edit-post-panels';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Gets the legacy local storage data for a given user.
|
12
|
+
*
|
13
|
+
* @param {string | number} userId The user id.
|
14
|
+
*
|
15
|
+
* @return {Object | null} The local storage data.
|
16
|
+
*/
|
17
|
+
function getLegacyData( userId ) {
|
18
|
+
const key = `WP_DATA_USER_${ userId }`;
|
19
|
+
const unparsedData = window.localStorage.getItem( key );
|
20
|
+
return JSON.parse( unparsedData );
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Converts data from the old `@wordpress/data` package format.
|
25
|
+
*
|
26
|
+
* @param {Object | null | undefined} data The legacy data in its original format.
|
27
|
+
*
|
28
|
+
* @return {Object | undefined} The converted data or `undefined` if there was
|
29
|
+
* nothing to convert.
|
30
|
+
*/
|
31
|
+
export function convertLegacyData( data ) {
|
32
|
+
if ( ! data ) {
|
33
|
+
return;
|
34
|
+
}
|
35
|
+
|
36
|
+
// Move boolean feature preferences from each editor into the
|
37
|
+
// preferences store data structure.
|
38
|
+
data = moveFeaturePreferences( data, 'core/edit-widgets' );
|
39
|
+
data = moveFeaturePreferences( data, 'core/customize-widgets' );
|
40
|
+
data = moveFeaturePreferences( data, 'core/edit-post' );
|
41
|
+
data = moveFeaturePreferences( data, 'core/edit-site' );
|
42
|
+
|
43
|
+
// Move third party boolean feature preferences from the interface package
|
44
|
+
// to the preferences store data structure.
|
45
|
+
data = moveThirdPartyFeaturePreferences( data );
|
46
|
+
|
47
|
+
// Move and convert the interface store's `enableItems` data into the
|
48
|
+
// preferences data structure.
|
49
|
+
data = moveInterfaceEnableItems( data );
|
50
|
+
|
51
|
+
// Move individual ad-hoc preferences from various packages into the
|
52
|
+
// preferences store data structure.
|
53
|
+
data = moveIndividualPreference(
|
54
|
+
data,
|
55
|
+
{ from: 'core/edit-post', to: 'core/edit-post' },
|
56
|
+
'hiddenBlockTypes'
|
57
|
+
);
|
58
|
+
data = moveIndividualPreference(
|
59
|
+
data,
|
60
|
+
{ from: 'core/edit-post', to: 'core/edit-post' },
|
61
|
+
'editorMode'
|
62
|
+
);
|
63
|
+
data = moveIndividualPreference(
|
64
|
+
data,
|
65
|
+
{ from: 'core/edit-post', to: 'core/edit-post' },
|
66
|
+
'preferredStyleVariations'
|
67
|
+
);
|
68
|
+
data = moveIndividualPreference(
|
69
|
+
data,
|
70
|
+
{ from: 'core/edit-post', to: 'core/edit-post' },
|
71
|
+
'panels',
|
72
|
+
convertEditPostPanels
|
73
|
+
);
|
74
|
+
data = moveIndividualPreference(
|
75
|
+
data,
|
76
|
+
{ from: 'core/editor', to: 'core/edit-post' },
|
77
|
+
'isPublishSidebarEnabled'
|
78
|
+
);
|
79
|
+
data = moveIndividualPreference(
|
80
|
+
data,
|
81
|
+
{ from: 'core/edit-site', to: 'core/edit-site' },
|
82
|
+
'editorMode'
|
83
|
+
);
|
84
|
+
|
85
|
+
// The new system is only concerned with persisting
|
86
|
+
// 'core/preferences' preferences reducer, so only return that.
|
87
|
+
return data?.[ 'core/preferences' ]?.preferences;
|
88
|
+
}
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Gets the legacy local storage data for the given user and returns the
|
92
|
+
* data converted to the new format.
|
93
|
+
*
|
94
|
+
* @param {string | number} userId The user id.
|
95
|
+
*
|
96
|
+
* @return {Object | undefined} The converted data or undefined if no local
|
97
|
+
* storage data could be found.
|
98
|
+
*/
|
99
|
+
export default function convertLegacyLocalStorageData( userId ) {
|
100
|
+
const data = getLegacyData( userId );
|
101
|
+
return convertLegacyData( data );
|
102
|
+
}
|
@@ -0,0 +1,135 @@
|
|
1
|
+
/**
|
2
|
+
* Move the 'features' object in local storage from the sourceStoreName to the
|
3
|
+
* preferences store data structure.
|
4
|
+
*
|
5
|
+
* Previously, editors used a data structure like this for feature preferences:
|
6
|
+
* ```js
|
7
|
+
* {
|
8
|
+
* 'core/edit-post': {
|
9
|
+
* preferences: {
|
10
|
+
* features; {
|
11
|
+
* topToolbar: true,
|
12
|
+
* // ... other boolean 'feature' preferences
|
13
|
+
* },
|
14
|
+
* },
|
15
|
+
* },
|
16
|
+
* }
|
17
|
+
* ```
|
18
|
+
*
|
19
|
+
* And for a while these feature preferences lived in the interface package:
|
20
|
+
* ```js
|
21
|
+
* {
|
22
|
+
* 'core/interface': {
|
23
|
+
* preferences: {
|
24
|
+
* features: {
|
25
|
+
* 'core/edit-post': {
|
26
|
+
* topToolbar: true
|
27
|
+
* }
|
28
|
+
* }
|
29
|
+
* }
|
30
|
+
* }
|
31
|
+
* }
|
32
|
+
* ```
|
33
|
+
*
|
34
|
+
* In the preferences store, 'features' aren't considered special, they're
|
35
|
+
* merged to the root level of the scope along with other preferences:
|
36
|
+
* ```js
|
37
|
+
* {
|
38
|
+
* 'core/preferences': {
|
39
|
+
* preferences: {
|
40
|
+
* 'core/edit-post': {
|
41
|
+
* topToolbar: true,
|
42
|
+
* // ... any other preferences.
|
43
|
+
* }
|
44
|
+
* }
|
45
|
+
* }
|
46
|
+
* }
|
47
|
+
* ```
|
48
|
+
*
|
49
|
+
* This function handles moving from either the source store or the interface
|
50
|
+
* store to the preferences data structure.
|
51
|
+
*
|
52
|
+
* @param {Object} state The state before migration.
|
53
|
+
* @param {string} sourceStoreName The name of the store that has persisted
|
54
|
+
* preferences to migrate to the preferences
|
55
|
+
* package.
|
56
|
+
* @return {Object} The migrated state
|
57
|
+
*/
|
58
|
+
export default function moveFeaturePreferences( state, sourceStoreName ) {
|
59
|
+
const preferencesStoreName = 'core/preferences';
|
60
|
+
const interfaceStoreName = 'core/interface';
|
61
|
+
|
62
|
+
// Features most recently (and briefly) lived in the interface package.
|
63
|
+
// If data exists there, prioritize using that for the migration. If not
|
64
|
+
// also check the original package as the user may have updated from an
|
65
|
+
// older block editor version.
|
66
|
+
const interfaceFeatures =
|
67
|
+
state?.[ interfaceStoreName ]?.preferences?.features?.[
|
68
|
+
sourceStoreName
|
69
|
+
];
|
70
|
+
const sourceFeatures = state?.[ sourceStoreName ]?.preferences?.features;
|
71
|
+
const featuresToMigrate = interfaceFeatures
|
72
|
+
? interfaceFeatures
|
73
|
+
: sourceFeatures;
|
74
|
+
|
75
|
+
if ( ! featuresToMigrate ) {
|
76
|
+
return state;
|
77
|
+
}
|
78
|
+
|
79
|
+
const existingPreferences = state?.[ preferencesStoreName ]?.preferences;
|
80
|
+
|
81
|
+
// Avoid migrating features again if they've previously been migrated.
|
82
|
+
if ( existingPreferences?.[ sourceStoreName ] ) {
|
83
|
+
return state;
|
84
|
+
}
|
85
|
+
|
86
|
+
let updatedInterfaceState;
|
87
|
+
if ( interfaceFeatures ) {
|
88
|
+
const otherInterfaceState = state?.[ interfaceStoreName ];
|
89
|
+
const otherInterfaceScopes =
|
90
|
+
state?.[ interfaceStoreName ]?.preferences?.features;
|
91
|
+
|
92
|
+
updatedInterfaceState = {
|
93
|
+
[ interfaceStoreName ]: {
|
94
|
+
...otherInterfaceState,
|
95
|
+
preferences: {
|
96
|
+
features: {
|
97
|
+
...otherInterfaceScopes,
|
98
|
+
[ sourceStoreName ]: undefined,
|
99
|
+
},
|
100
|
+
},
|
101
|
+
},
|
102
|
+
};
|
103
|
+
}
|
104
|
+
|
105
|
+
let updatedSourceState;
|
106
|
+
if ( sourceFeatures ) {
|
107
|
+
const otherSourceState = state?.[ sourceStoreName ];
|
108
|
+
const sourcePreferences = state?.[ sourceStoreName ]?.preferences;
|
109
|
+
|
110
|
+
updatedSourceState = {
|
111
|
+
[ sourceStoreName ]: {
|
112
|
+
...otherSourceState,
|
113
|
+
preferences: {
|
114
|
+
...sourcePreferences,
|
115
|
+
features: undefined,
|
116
|
+
},
|
117
|
+
},
|
118
|
+
};
|
119
|
+
}
|
120
|
+
|
121
|
+
// Set the feature values in the interface store, the features
|
122
|
+
// object is keyed by 'scope', which matches the store name for
|
123
|
+
// the source.
|
124
|
+
return {
|
125
|
+
...state,
|
126
|
+
[ preferencesStoreName ]: {
|
127
|
+
preferences: {
|
128
|
+
...existingPreferences,
|
129
|
+
[ sourceStoreName ]: featuresToMigrate,
|
130
|
+
},
|
131
|
+
},
|
132
|
+
...updatedInterfaceState,
|
133
|
+
...updatedSourceState,
|
134
|
+
};
|
135
|
+
}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
const identity = ( arg ) => arg;
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Migrates an individual item inside the `preferences` object for a package's store.
|
5
|
+
*
|
6
|
+
* Previously, some packages had individual 'preferences' of any data type, and many used
|
7
|
+
* complex nested data structures. For example:
|
8
|
+
* ```js
|
9
|
+
* {
|
10
|
+
* 'core/edit-post': {
|
11
|
+
* preferences: {
|
12
|
+
* panels: {
|
13
|
+
* publish: {
|
14
|
+
* opened: true,
|
15
|
+
* enabled: true,
|
16
|
+
* }
|
17
|
+
* },
|
18
|
+
* // ...other preferences.
|
19
|
+
* },
|
20
|
+
* },
|
21
|
+
* }
|
22
|
+
*
|
23
|
+
* This function supports moving an individual preference like 'panels' above into the
|
24
|
+
* preferences package data structure.
|
25
|
+
*
|
26
|
+
* It supports moving a preference to a particular scope in the preferences store and
|
27
|
+
* optionally converting the data using a `convert` function.
|
28
|
+
*
|
29
|
+
* ```
|
30
|
+
*
|
31
|
+
* @param {Object} state The original state.
|
32
|
+
* @param {Object} migrate An options object that contains details of the migration.
|
33
|
+
* @param {string} migrate.from The name of the store to migrate from.
|
34
|
+
* @param {string} migrate.to The scope in the preferences store to migrate to.
|
35
|
+
* @param {string} key The key in the preferences object to migrate.
|
36
|
+
* @param {?Function} convert A function that converts preferences from one format to another.
|
37
|
+
*/
|
38
|
+
export default function moveIndividualPreferenceToPreferences(
|
39
|
+
state,
|
40
|
+
{ from: sourceStoreName, to: scope },
|
41
|
+
key,
|
42
|
+
convert = identity
|
43
|
+
) {
|
44
|
+
const preferencesStoreName = 'core/preferences';
|
45
|
+
const sourcePreference = state?.[ sourceStoreName ]?.preferences?.[ key ];
|
46
|
+
|
47
|
+
// There's nothing to migrate, exit early.
|
48
|
+
if ( sourcePreference === undefined ) {
|
49
|
+
return state;
|
50
|
+
}
|
51
|
+
|
52
|
+
const targetPreference =
|
53
|
+
state?.[ preferencesStoreName ]?.preferences?.[ scope ]?.[ key ];
|
54
|
+
|
55
|
+
// There's existing data at the target, so don't overwrite it, exit early.
|
56
|
+
if ( targetPreference ) {
|
57
|
+
return state;
|
58
|
+
}
|
59
|
+
|
60
|
+
const otherScopes = state?.[ preferencesStoreName ]?.preferences;
|
61
|
+
const otherPreferences =
|
62
|
+
state?.[ preferencesStoreName ]?.preferences?.[ scope ];
|
63
|
+
|
64
|
+
const otherSourceState = state?.[ sourceStoreName ];
|
65
|
+
const allSourcePreferences = state?.[ sourceStoreName ]?.preferences;
|
66
|
+
|
67
|
+
// Pass an object with the key and value as this allows the convert
|
68
|
+
// function to convert to a data structure that has different keys.
|
69
|
+
const convertedPreferences = convert( { [ key ]: sourcePreference } );
|
70
|
+
|
71
|
+
return {
|
72
|
+
...state,
|
73
|
+
[ preferencesStoreName ]: {
|
74
|
+
preferences: {
|
75
|
+
...otherScopes,
|
76
|
+
[ scope ]: {
|
77
|
+
...otherPreferences,
|
78
|
+
...convertedPreferences,
|
79
|
+
},
|
80
|
+
},
|
81
|
+
},
|
82
|
+
[ sourceStoreName ]: {
|
83
|
+
...otherSourceState,
|
84
|
+
preferences: {
|
85
|
+
...allSourcePreferences,
|
86
|
+
[ key ]: undefined,
|
87
|
+
},
|
88
|
+
},
|
89
|
+
};
|
90
|
+
}
|