@wordpress/core-data 7.50.1-next.v.202607070741.0 → 7.52.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 +12 -0
- package/build/actions.cjs +54 -1
- package/build/actions.cjs.map +3 -3
- package/build/components/entities-saved-states/entity-record-item.cjs +67 -0
- package/build/components/entities-saved-states/entity-record-item.cjs.map +7 -0
- package/build/components/entities-saved-states/entity-type-list.cjs +135 -0
- package/build/components/entities-saved-states/entity-type-list.cjs.map +7 -0
- package/build/components/entities-saved-states/hooks/use-is-dirty.cjs +97 -0
- package/build/components/entities-saved-states/hooks/use-is-dirty.cjs.map +7 -0
- package/build/components/entities-saved-states/index.cjs +215 -0
- package/build/components/entities-saved-states/index.cjs.map +7 -0
- package/build/entities.cjs +3 -1
- package/build/entities.cjs.map +2 -2
- package/build/private-actions.cjs +85 -0
- package/build/private-actions.cjs.map +3 -3
- package/build/private-apis.cjs +19 -0
- package/build/private-apis.cjs.map +3 -3
- package/build/utils/crdt.cjs +4 -2
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/get-template-info.cjs +53 -0
- package/build/utils/get-template-info.cjs.map +7 -0
- package/build/utils/get-template-part-icon.cjs +43 -0
- package/build/utils/get-template-part-icon.cjs.map +7 -0
- package/build-module/actions.mjs +57 -1
- package/build-module/actions.mjs.map +2 -2
- package/build-module/components/entities-saved-states/entity-record-item.mjs +46 -0
- package/build-module/components/entities-saved-states/entity-record-item.mjs.map +7 -0
- package/build-module/components/entities-saved-states/entity-type-list.mjs +104 -0
- package/build-module/components/entities-saved-states/entity-type-list.mjs.map +7 -0
- package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs +72 -0
- package/build-module/components/entities-saved-states/hooks/use-is-dirty.mjs.map +7 -0
- package/build-module/components/entities-saved-states/index.mjs +187 -0
- package/build-module/components/entities-saved-states/index.mjs.map +7 -0
- package/build-module/entities.mjs +3 -1
- package/build-module/entities.mjs.map +2 -2
- package/build-module/private-actions.mjs +84 -0
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +12 -0
- package/build-module/private-apis.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +2 -1
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/get-template-info.mjs +28 -0
- package/build-module/utils/get-template-info.mjs.map +7 -0
- package/build-module/utils/get-template-part-icon.mjs +24 -0
- package/build-module/utils/get-template-part-icon.mjs.map +7 -0
- package/build-types/actions.d.ts +3 -3
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/base-awareness.d.ts +3 -0
- package/build-types/awareness/base-awareness.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +7 -0
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/batch/create-batch.d.ts +1 -1
- package/build-types/batch/create-batch.d.ts.map +1 -1
- package/build-types/components/entities-saved-states/entity-record-item.d.ts +6 -0
- package/build-types/components/entities-saved-states/entity-record-item.d.ts.map +1 -0
- package/build-types/components/entities-saved-states/entity-type-list.d.ts +6 -0
- package/build-types/components/entities-saved-states/entity-type-list.d.ts.map +1 -0
- package/build-types/components/entities-saved-states/hooks/use-is-dirty.d.ts +11 -0
- package/build-types/components/entities-saved-states/hooks/use-is-dirty.d.ts.map +1 -0
- package/build-types/components/entities-saved-states/index.d.ts +49 -0
- package/build-types/components/entities-saved-states/index.d.ts.map +1 -0
- package/build-types/entities.d.ts +2 -2
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/entity-provider.d.ts +1 -1
- package/build-types/entity-provider.d.ts.map +1 -1
- package/build-types/entity-types/attachment.d.ts.map +1 -1
- package/build-types/entity-types/base-entity-records.d.ts.map +1 -1
- package/build-types/entity-types/base.d.ts.map +1 -1
- package/build-types/entity-types/comment.d.ts.map +1 -1
- package/build-types/entity-types/font-collection.d.ts.map +1 -1
- package/build-types/entity-types/font-family.d.ts.map +1 -1
- package/build-types/entity-types/global-styles-revision.d.ts.map +1 -1
- package/build-types/entity-types/icon.d.ts.map +1 -1
- package/build-types/entity-types/menu-location.d.ts.map +1 -1
- package/build-types/entity-types/nav-menu-item.d.ts.map +1 -1
- package/build-types/entity-types/nav-menu.d.ts.map +1 -1
- package/build-types/entity-types/page.d.ts.map +1 -1
- package/build-types/entity-types/plugin.d.ts.map +1 -1
- package/build-types/entity-types/post-revision.d.ts.map +1 -1
- package/build-types/entity-types/post-status.d.ts.map +1 -1
- package/build-types/entity-types/post.d.ts.map +1 -1
- package/build-types/entity-types/settings.d.ts.map +1 -1
- package/build-types/entity-types/sidebar.d.ts.map +1 -1
- package/build-types/entity-types/taxonomy.d.ts.map +1 -1
- package/build-types/entity-types/term.d.ts.map +1 -1
- package/build-types/entity-types/theme.d.ts.map +1 -1
- package/build-types/entity-types/type.d.ts.map +1 -1
- package/build-types/entity-types/user.d.ts.map +1 -1
- package/build-types/entity-types/widget-type.d.ts.map +1 -1
- package/build-types/entity-types/widget.d.ts.map +1 -1
- package/build-types/entity-types/wp-template-part.d.ts.map +1 -1
- package/build-types/entity-types/wp-template.d.ts.map +1 -1
- package/build-types/fetch/__experimental-fetch-url-data.d.ts +3 -0
- package/build-types/fetch/__experimental-fetch-url-data.d.ts.map +1 -1
- package/build-types/index.d.ts +17 -47
- package/build-types/index.d.ts.map +1 -1
- package/build-types/locks/actions.d.ts +1 -1
- package/build-types/locks/engine.d.ts +1 -1
- package/build-types/locks/utils.d.ts +1 -1
- package/build-types/locks/utils.d.ts.map +1 -1
- package/build-types/private-actions.d.ts +32 -1
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-apis.d.ts +9 -0
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/queried-data/actions.d.ts +1 -1
- package/build-types/queried-data/actions.d.ts.map +1 -1
- package/build-types/queried-data/get-query-parts.d.ts +21 -0
- package/build-types/queried-data/get-query-parts.d.ts.map +1 -1
- package/build-types/reducer.d.ts +3 -3
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts +1 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +8 -0
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/get-template-info.d.ts +13 -0
- package/build-types/utils/get-template-info.d.ts.map +1 -0
- package/build-types/utils/get-template-part-icon.d.ts +9 -0
- package/build-types/utils/get-template-part-icon.d.ts.map +1 -0
- package/build-types/utils/log-entity-deprecation.d.ts +1 -1
- package/build-types/utils/log-entity-deprecation.d.ts.map +1 -1
- package/build-types/utils/on-sub-key.d.ts +4 -0
- package/build-types/utils/on-sub-key.d.ts.map +1 -0
- package/package.json +30 -25
- package/src/actions.js +92 -1
- package/src/components/entities-saved-states/entity-record-item.js +60 -0
- package/src/components/entities-saved-states/entity-type-list.js +130 -0
- package/src/components/entities-saved-states/hooks/use-is-dirty.js +101 -0
- package/src/components/entities-saved-states/index.js +242 -0
- package/src/components/entities-saved-states/style.scss +70 -0
- package/src/components/entities-saved-states/test/use-is-dirty.js +88 -0
- package/src/entities.js +3 -1
- package/src/private-actions.js +127 -0
- package/src/private-apis.ts +12 -0
- package/src/style.scss +1 -0
- package/src/test/actions.js +587 -0
- package/src/utils/crdt.ts +1 -1
- package/src/utils/get-template-info.js +52 -0
- package/src/utils/get-template-part-icon.js +30 -0
- package/src/utils/test/get-template-info.js +224 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { Button, Flex, FlexItem } from '@wordpress/components';
|
|
10
|
+
import { __, _n, sprintf } from '@wordpress/i18n';
|
|
11
|
+
import {
|
|
12
|
+
useCallback,
|
|
13
|
+
useRef,
|
|
14
|
+
createInterpolateElement,
|
|
15
|
+
} from '@wordpress/element';
|
|
16
|
+
import {
|
|
17
|
+
__experimentalUseDialog as useDialog,
|
|
18
|
+
useInstanceId,
|
|
19
|
+
} from '@wordpress/compose';
|
|
20
|
+
import { useDispatch } from '@wordpress/data';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Internal dependencies
|
|
24
|
+
*/
|
|
25
|
+
import EntityTypeList from './entity-type-list';
|
|
26
|
+
import { useIsDirty } from './hooks/use-is-dirty';
|
|
27
|
+
import { unlock } from '../../lock-unlock';
|
|
28
|
+
import { STORE_NAME } from '../../name';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Renders the component for managing saved states of entities.
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} props The component props.
|
|
34
|
+
* @param {Function} props.close The function to close the dialog.
|
|
35
|
+
* @param {boolean=} props.renderDialog Whether to render the component with modal dialog behavior.
|
|
36
|
+
* @param {string} props.variant Changes the layout of the component. When an `inline` value is provided, the action buttons are rendered at the end of the component instead of at the start.
|
|
37
|
+
*
|
|
38
|
+
* @return {React.ReactNode} The rendered component.
|
|
39
|
+
*/
|
|
40
|
+
export default function EntitiesSavedStates( {
|
|
41
|
+
close,
|
|
42
|
+
renderDialog,
|
|
43
|
+
variant,
|
|
44
|
+
} ) {
|
|
45
|
+
const isDirtyProps = useIsDirty();
|
|
46
|
+
return (
|
|
47
|
+
<EntitiesSavedStatesExtensible
|
|
48
|
+
close={ close }
|
|
49
|
+
renderDialog={ renderDialog }
|
|
50
|
+
variant={ variant }
|
|
51
|
+
{ ...isDirtyProps }
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Renders a panel for saving entities with dirty records.
|
|
58
|
+
*
|
|
59
|
+
* @param {Object} props The component props.
|
|
60
|
+
* @param {string} props.additionalPrompt Additional prompt to display.
|
|
61
|
+
* @param {Function} props.close Function to close the panel.
|
|
62
|
+
* @param {Function} props.onSave Function to call when saving entities.
|
|
63
|
+
* @param {boolean} props.saveEnabled Flag indicating if save is enabled.
|
|
64
|
+
* @param {string} props.saveLabel Label for the save button.
|
|
65
|
+
* @param {boolean} props.renderDialog Whether to render the component with modal dialog behavior.
|
|
66
|
+
* @param {Array} props.dirtyEntityRecords Array of dirty entity records.
|
|
67
|
+
* @param {boolean} props.isDirty Flag indicating if there are dirty entities.
|
|
68
|
+
* @param {Function} props.setUnselectedEntities Function to set unselected entities.
|
|
69
|
+
* @param {Array} props.unselectedEntities Array of unselected entities.
|
|
70
|
+
* @param {string} props.variant Changes the layout of the component. When an `inline` value is provided, the action buttons are rendered at the end of the component instead of at the start.
|
|
71
|
+
* @param {string} props.successNoticeContent Optional custom success notice content.
|
|
72
|
+
*
|
|
73
|
+
* @return {React.ReactNode} The rendered component.
|
|
74
|
+
*/
|
|
75
|
+
export function EntitiesSavedStatesExtensible( {
|
|
76
|
+
additionalPrompt = undefined,
|
|
77
|
+
close,
|
|
78
|
+
onSave = undefined,
|
|
79
|
+
saveEnabled: saveEnabledProp = undefined,
|
|
80
|
+
saveLabel = __( 'Save' ),
|
|
81
|
+
renderDialog,
|
|
82
|
+
dirtyEntityRecords,
|
|
83
|
+
isDirty,
|
|
84
|
+
setUnselectedEntities,
|
|
85
|
+
unselectedEntities,
|
|
86
|
+
variant = 'default',
|
|
87
|
+
successNoticeContent,
|
|
88
|
+
} ) {
|
|
89
|
+
const saveButtonRef = useRef();
|
|
90
|
+
const { saveDirtyEntities } = unlock( useDispatch( STORE_NAME ) );
|
|
91
|
+
// To group entities by type.
|
|
92
|
+
const partitionedSavables = dirtyEntityRecords.reduce( ( acc, record ) => {
|
|
93
|
+
const { name } = record;
|
|
94
|
+
if ( ! acc[ name ] ) {
|
|
95
|
+
acc[ name ] = [];
|
|
96
|
+
}
|
|
97
|
+
acc[ name ].push( record );
|
|
98
|
+
return acc;
|
|
99
|
+
}, {} );
|
|
100
|
+
|
|
101
|
+
// Sort entity groups.
|
|
102
|
+
const {
|
|
103
|
+
site: siteSavables,
|
|
104
|
+
wp_template: templateSavables,
|
|
105
|
+
wp_template_part: templatePartSavables,
|
|
106
|
+
...contentSavables
|
|
107
|
+
} = partitionedSavables;
|
|
108
|
+
const sortedPartitionedSavables = [
|
|
109
|
+
siteSavables,
|
|
110
|
+
templateSavables,
|
|
111
|
+
templatePartSavables,
|
|
112
|
+
...Object.values( contentSavables ),
|
|
113
|
+
].filter( Array.isArray );
|
|
114
|
+
|
|
115
|
+
const saveEnabled = saveEnabledProp ?? isDirty;
|
|
116
|
+
// Explicitly define this with no argument passed. Using `close` on
|
|
117
|
+
// its own will use the event object in place of the expected saved entities.
|
|
118
|
+
const dismissPanel = useCallback( () => close(), [ close ] );
|
|
119
|
+
|
|
120
|
+
const [ saveDialogRef, saveDialogProps ] = useDialog( {
|
|
121
|
+
onClose: () => dismissPanel(),
|
|
122
|
+
} );
|
|
123
|
+
const dialogLabelId = useInstanceId(
|
|
124
|
+
EntitiesSavedStatesExtensible,
|
|
125
|
+
'entities-saved-states__panel-label'
|
|
126
|
+
);
|
|
127
|
+
const dialogDescriptionId = useInstanceId(
|
|
128
|
+
EntitiesSavedStatesExtensible,
|
|
129
|
+
'entities-saved-states__panel-description'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const selectItemsToSaveDescription = !! dirtyEntityRecords.length
|
|
133
|
+
? __( 'Select the items you want to save.' )
|
|
134
|
+
: undefined;
|
|
135
|
+
|
|
136
|
+
const isInline = variant === 'inline';
|
|
137
|
+
|
|
138
|
+
const actionButtons = (
|
|
139
|
+
<>
|
|
140
|
+
<FlexItem
|
|
141
|
+
isBlock={ isInline ? false : true }
|
|
142
|
+
as={ Button }
|
|
143
|
+
variant={ isInline ? 'tertiary' : 'secondary' }
|
|
144
|
+
size={ isInline ? undefined : 'compact' }
|
|
145
|
+
onClick={ dismissPanel }
|
|
146
|
+
>
|
|
147
|
+
{ __( 'Cancel' ) }
|
|
148
|
+
</FlexItem>
|
|
149
|
+
<FlexItem
|
|
150
|
+
isBlock={ isInline ? false : true }
|
|
151
|
+
as={ Button }
|
|
152
|
+
ref={ saveButtonRef }
|
|
153
|
+
variant="primary"
|
|
154
|
+
size={ isInline ? undefined : 'compact' }
|
|
155
|
+
disabled={ ! saveEnabled }
|
|
156
|
+
accessibleWhenDisabled
|
|
157
|
+
onClick={ () =>
|
|
158
|
+
saveDirtyEntities( {
|
|
159
|
+
onSave,
|
|
160
|
+
dirtyEntityRecords,
|
|
161
|
+
entitiesToSkip: unselectedEntities,
|
|
162
|
+
close,
|
|
163
|
+
successNoticeContent,
|
|
164
|
+
} )
|
|
165
|
+
}
|
|
166
|
+
className="entities-saved-states__save-button"
|
|
167
|
+
>
|
|
168
|
+
{ saveLabel }
|
|
169
|
+
</FlexItem>
|
|
170
|
+
</>
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div
|
|
175
|
+
ref={ renderDialog ? saveDialogRef : undefined }
|
|
176
|
+
{ ...( renderDialog && saveDialogProps ) }
|
|
177
|
+
className={ clsx( 'entities-saved-states__panel', {
|
|
178
|
+
'is-inline': isInline,
|
|
179
|
+
} ) }
|
|
180
|
+
role={ renderDialog ? 'dialog' : undefined }
|
|
181
|
+
aria-labelledby={ renderDialog ? dialogLabelId : undefined }
|
|
182
|
+
aria-describedby={ renderDialog ? dialogDescriptionId : undefined }
|
|
183
|
+
>
|
|
184
|
+
{ ! isInline && (
|
|
185
|
+
<Flex className="entities-saved-states__panel-header" gap={ 2 }>
|
|
186
|
+
{ actionButtons }
|
|
187
|
+
</Flex>
|
|
188
|
+
) }
|
|
189
|
+
|
|
190
|
+
<div className="entities-saved-states__text-prompt">
|
|
191
|
+
<div className="entities-saved-states__text-prompt--header-wrapper">
|
|
192
|
+
<strong
|
|
193
|
+
id={ renderDialog ? dialogLabelId : undefined }
|
|
194
|
+
className="entities-saved-states__text-prompt--header"
|
|
195
|
+
>
|
|
196
|
+
{ __( 'Are you ready to save?' ) }
|
|
197
|
+
</strong>
|
|
198
|
+
</div>
|
|
199
|
+
<div id={ renderDialog ? dialogDescriptionId : undefined }>
|
|
200
|
+
{ additionalPrompt }
|
|
201
|
+
<p className="entities-saved-states__text-prompt--changes-count">
|
|
202
|
+
{ isDirty
|
|
203
|
+
? createInterpolateElement(
|
|
204
|
+
sprintf(
|
|
205
|
+
/* translators: %d: number of site changes waiting to be saved. */
|
|
206
|
+
_n(
|
|
207
|
+
'There is <strong>%d site change</strong> waiting to be saved.',
|
|
208
|
+
'There are <strong>%d site changes</strong> waiting to be saved.',
|
|
209
|
+
dirtyEntityRecords.length
|
|
210
|
+
),
|
|
211
|
+
dirtyEntityRecords.length
|
|
212
|
+
),
|
|
213
|
+
{ strong: <strong /> }
|
|
214
|
+
)
|
|
215
|
+
: selectItemsToSaveDescription }
|
|
216
|
+
</p>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{ sortedPartitionedSavables.map( ( list ) => {
|
|
221
|
+
return (
|
|
222
|
+
<EntityTypeList
|
|
223
|
+
key={ list[ 0 ].name }
|
|
224
|
+
list={ list }
|
|
225
|
+
unselectedEntities={ unselectedEntities }
|
|
226
|
+
setUnselectedEntities={ setUnselectedEntities }
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
} ) }
|
|
230
|
+
|
|
231
|
+
{ isInline && (
|
|
232
|
+
<Flex
|
|
233
|
+
direction="row"
|
|
234
|
+
justify="flex-end"
|
|
235
|
+
className="entities-saved-states__panel-footer"
|
|
236
|
+
>
|
|
237
|
+
{ actionButtons }
|
|
238
|
+
</Flex>
|
|
239
|
+
) }
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
@use "@wordpress/base-styles/colors" as *;
|
|
2
|
+
@use "@wordpress/base-styles/variables" as *;
|
|
3
|
+
|
|
4
|
+
.entities-saved-states__panel-header {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
background: $white;
|
|
7
|
+
padding-left: $grid-unit-20;
|
|
8
|
+
padding-right: $grid-unit-20;
|
|
9
|
+
height: $header-height;
|
|
10
|
+
border-bottom: $border-width solid $gray-300;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.entities-saved-states__text-prompt {
|
|
14
|
+
padding: $grid-unit-20;
|
|
15
|
+
padding-bottom: $grid-unit-05;
|
|
16
|
+
.entities-saved-states__text-prompt--header {
|
|
17
|
+
display: block;
|
|
18
|
+
margin-bottom: $grid-unit-15;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.entities-saved-states__panel.is-inline {
|
|
23
|
+
.entities-saved-states__text-prompt {
|
|
24
|
+
padding: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.entities-saved-states__panel-body {
|
|
28
|
+
padding-left: 0;
|
|
29
|
+
padding-right: 0;
|
|
30
|
+
border: 0;
|
|
31
|
+
|
|
32
|
+
> h2 {
|
|
33
|
+
margin-left: -1 * $grid-unit-20;
|
|
34
|
+
margin-right: -1 * $grid-unit-20;
|
|
35
|
+
margin-bottom: 0;
|
|
36
|
+
|
|
37
|
+
button {
|
|
38
|
+
font-size: $font-size-x-small;
|
|
39
|
+
text-transform: uppercase;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.entities-saved-states__text-prompt--header-wrapper {
|
|
45
|
+
display: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.entities-saved-states__text-prompt--changes-count {
|
|
49
|
+
margin-top: 0;
|
|
50
|
+
margin-bottom: $grid-unit-10;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.entities-saved-states__panel-footer {
|
|
54
|
+
margin-top: $grid-unit-20;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.entities-saved-states__change-control {
|
|
59
|
+
flex: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.entities-saved-states__changes {
|
|
63
|
+
font-size: $default-font-size;
|
|
64
|
+
margin: $grid-unit-05 $grid-unit-20 0 $grid-unit-30;
|
|
65
|
+
list-style: disc;
|
|
66
|
+
|
|
67
|
+
li {
|
|
68
|
+
margin-bottom: $grid-unit-05;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { act, renderHook } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { useIsDirty } from '../hooks/use-is-dirty';
|
|
10
|
+
|
|
11
|
+
jest.mock( '@wordpress/data', () => {
|
|
12
|
+
return {
|
|
13
|
+
useSelect: jest.fn().mockImplementation( ( fn ) => {
|
|
14
|
+
const select = () => {
|
|
15
|
+
return {
|
|
16
|
+
__experimentalGetDirtyEntityRecords: jest
|
|
17
|
+
.fn()
|
|
18
|
+
.mockReturnValue( [
|
|
19
|
+
{
|
|
20
|
+
kind: 'root',
|
|
21
|
+
name: 'site',
|
|
22
|
+
title: 'title',
|
|
23
|
+
property: 'property',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
kind: 'postType',
|
|
27
|
+
name: 'post',
|
|
28
|
+
title: 'title',
|
|
29
|
+
property: 'property',
|
|
30
|
+
},
|
|
31
|
+
] ),
|
|
32
|
+
getEntityRecordEdits: jest.fn().mockReturnValue( {
|
|
33
|
+
title: 'My Site',
|
|
34
|
+
} ),
|
|
35
|
+
getEntityConfig: jest.fn().mockReturnValue( {
|
|
36
|
+
meta: { labels: { title: 'Title' } },
|
|
37
|
+
} ),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
return fn( select );
|
|
41
|
+
} ),
|
|
42
|
+
};
|
|
43
|
+
} );
|
|
44
|
+
|
|
45
|
+
describe( 'useIsDirty', () => {
|
|
46
|
+
it( 'should calculate dirtyEntityRecords', () => {
|
|
47
|
+
const { result } = renderHook( () => useIsDirty() );
|
|
48
|
+
expect( result.current.dirtyEntityRecords ).toEqual( [
|
|
49
|
+
{
|
|
50
|
+
kind: 'postType',
|
|
51
|
+
name: 'post',
|
|
52
|
+
property: 'property',
|
|
53
|
+
title: 'title',
|
|
54
|
+
},
|
|
55
|
+
{ kind: 'root', name: 'site', property: 'title', title: 'Title' },
|
|
56
|
+
] );
|
|
57
|
+
} );
|
|
58
|
+
it( 'should return `isDirty: true` when there are changes', () => {
|
|
59
|
+
const { result } = renderHook( () => useIsDirty() );
|
|
60
|
+
expect( result.current.isDirty ).toBeTruthy();
|
|
61
|
+
} );
|
|
62
|
+
it( 'should return `isDirty: false` when there are NO changes', async () => {
|
|
63
|
+
const { result } = renderHook( () => useIsDirty() );
|
|
64
|
+
act( () => {
|
|
65
|
+
result.current.setUnselectedEntities(
|
|
66
|
+
{
|
|
67
|
+
kind: 'postType',
|
|
68
|
+
name: 'post',
|
|
69
|
+
key: 'key',
|
|
70
|
+
property: 'property',
|
|
71
|
+
},
|
|
72
|
+
false
|
|
73
|
+
);
|
|
74
|
+
} );
|
|
75
|
+
act( () => {
|
|
76
|
+
result.current.setUnselectedEntities(
|
|
77
|
+
{
|
|
78
|
+
kind: 'root',
|
|
79
|
+
name: 'site',
|
|
80
|
+
key: 'key',
|
|
81
|
+
property: 'property',
|
|
82
|
+
},
|
|
83
|
+
false
|
|
84
|
+
);
|
|
85
|
+
} );
|
|
86
|
+
expect( result.current.isDirty ).toBeFalsy();
|
|
87
|
+
} );
|
|
88
|
+
} );
|
package/src/entities.js
CHANGED
|
@@ -49,8 +49,10 @@ export const rootEntitiesConfig = [
|
|
|
49
49
|
'description',
|
|
50
50
|
'gmt_offset',
|
|
51
51
|
'home',
|
|
52
|
+
'image_max_bit_depth',
|
|
52
53
|
'image_sizes',
|
|
53
54
|
'image_size_threshold',
|
|
55
|
+
'image_strip_meta',
|
|
54
56
|
'name',
|
|
55
57
|
'site_icon',
|
|
56
58
|
'site_icon_url',
|
|
@@ -323,7 +325,7 @@ export const prePersistPostType = async (
|
|
|
323
325
|
if ( persistedRecord ) {
|
|
324
326
|
const objectType = `postType/${ name }`;
|
|
325
327
|
const objectId = persistedRecord.id;
|
|
326
|
-
const serializedDoc = getSyncManager()?.createPersistedCRDTDoc(
|
|
328
|
+
const serializedDoc = await getSyncManager()?.createPersistedCRDTDoc(
|
|
327
329
|
objectType,
|
|
328
330
|
objectId
|
|
329
331
|
);
|
package/src/private-actions.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
4
|
import apiFetch from '@wordpress/api-fetch';
|
|
5
|
+
import { store as noticesStore } from '@wordpress/notices';
|
|
6
|
+
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Internal dependencies
|
|
@@ -247,3 +250,127 @@ export function setSyncConnectionStatus( kind, name, key, status ) {
|
|
|
247
250
|
status,
|
|
248
251
|
};
|
|
249
252
|
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Save entity records marked as dirty.
|
|
256
|
+
*
|
|
257
|
+
* @param {Object} options Options for the action.
|
|
258
|
+
* @param {Function} [options.onSave] Callback when saving happens.
|
|
259
|
+
* @param {object[]} [options.dirtyEntityRecords] Array of dirty entities.
|
|
260
|
+
* @param {object[]} [options.entitiesToSkip] Array of entities to skip saving.
|
|
261
|
+
* @param {Function} [options.close] Callback when the actions is called. It should be consolidated with `onSave`.
|
|
262
|
+
* @param {string} [options.successNoticeContent] Optional custom success notice content. Defaults to 'Site updated.'.
|
|
263
|
+
*/
|
|
264
|
+
export const saveDirtyEntities =
|
|
265
|
+
( {
|
|
266
|
+
onSave,
|
|
267
|
+
dirtyEntityRecords = [],
|
|
268
|
+
entitiesToSkip = [],
|
|
269
|
+
close,
|
|
270
|
+
successNoticeContent,
|
|
271
|
+
} = {} ) =>
|
|
272
|
+
( { registry } ) => {
|
|
273
|
+
const PUBLISH_ON_SAVE_ENTITIES = [
|
|
274
|
+
{ kind: 'postType', name: 'wp_navigation' },
|
|
275
|
+
];
|
|
276
|
+
const saveNoticeId = 'site-editor-save-success';
|
|
277
|
+
const homeUrl = registry
|
|
278
|
+
.select( STORE_NAME )
|
|
279
|
+
.getEntityRecord( 'root', '__unstableBase' )?.home;
|
|
280
|
+
registry.dispatch( noticesStore ).removeNotice( saveNoticeId );
|
|
281
|
+
const entitiesToSave = dirtyEntityRecords.filter(
|
|
282
|
+
( { kind, name, key, property } ) => {
|
|
283
|
+
return ! entitiesToSkip.some(
|
|
284
|
+
( elt ) =>
|
|
285
|
+
elt.kind === kind &&
|
|
286
|
+
elt.name === name &&
|
|
287
|
+
elt.key === key &&
|
|
288
|
+
elt.property === property
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
close?.( entitiesToSave );
|
|
293
|
+
const siteItemsToSave = [];
|
|
294
|
+
const pendingSavedRecords = [];
|
|
295
|
+
entitiesToSave.forEach( ( { kind, name, key, property } ) => {
|
|
296
|
+
if ( 'root' === kind && 'site' === name ) {
|
|
297
|
+
siteItemsToSave.push( property );
|
|
298
|
+
} else {
|
|
299
|
+
if (
|
|
300
|
+
PUBLISH_ON_SAVE_ENTITIES.some(
|
|
301
|
+
( typeToPublish ) =>
|
|
302
|
+
typeToPublish.kind === kind &&
|
|
303
|
+
typeToPublish.name === name
|
|
304
|
+
)
|
|
305
|
+
) {
|
|
306
|
+
registry
|
|
307
|
+
.dispatch( STORE_NAME )
|
|
308
|
+
.editEntityRecord( kind, name, key, {
|
|
309
|
+
status: 'publish',
|
|
310
|
+
} );
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
pendingSavedRecords.push(
|
|
314
|
+
registry
|
|
315
|
+
.dispatch( STORE_NAME )
|
|
316
|
+
.saveEditedEntityRecord( kind, name, key )
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
} );
|
|
320
|
+
if ( siteItemsToSave.length ) {
|
|
321
|
+
pendingSavedRecords.push(
|
|
322
|
+
registry
|
|
323
|
+
.dispatch( STORE_NAME )
|
|
324
|
+
.__experimentalSaveSpecifiedEntityEdits(
|
|
325
|
+
'root',
|
|
326
|
+
'site',
|
|
327
|
+
undefined,
|
|
328
|
+
siteItemsToSave
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
registry
|
|
333
|
+
.dispatch( blockEditorStore )
|
|
334
|
+
.__unstableMarkLastChangeAsPersistent();
|
|
335
|
+
|
|
336
|
+
Promise.all( pendingSavedRecords )
|
|
337
|
+
.then( async ( values ) => {
|
|
338
|
+
if ( onSave ) {
|
|
339
|
+
await onSave();
|
|
340
|
+
}
|
|
341
|
+
return values;
|
|
342
|
+
} )
|
|
343
|
+
.then( ( values ) => {
|
|
344
|
+
if (
|
|
345
|
+
values.some( ( value ) => typeof value === 'undefined' )
|
|
346
|
+
) {
|
|
347
|
+
registry
|
|
348
|
+
.dispatch( noticesStore )
|
|
349
|
+
.createErrorNotice( __( 'Saving failed.' ) );
|
|
350
|
+
} else {
|
|
351
|
+
registry
|
|
352
|
+
.dispatch( noticesStore )
|
|
353
|
+
.createSuccessNotice(
|
|
354
|
+
successNoticeContent || __( 'Site updated.' ),
|
|
355
|
+
{
|
|
356
|
+
type: 'snackbar',
|
|
357
|
+
id: saveNoticeId,
|
|
358
|
+
actions: [
|
|
359
|
+
{
|
|
360
|
+
label: __( 'View site' ),
|
|
361
|
+
url: homeUrl,
|
|
362
|
+
openInNewTab: true,
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
} )
|
|
369
|
+
.catch( ( error ) =>
|
|
370
|
+
registry
|
|
371
|
+
.dispatch( noticesStore )
|
|
372
|
+
.createErrorNotice(
|
|
373
|
+
`${ __( 'Saving failed.' ) } ${ error }`
|
|
374
|
+
)
|
|
375
|
+
);
|
|
376
|
+
};
|
package/src/private-apis.ts
CHANGED
|
@@ -10,6 +10,13 @@ import {
|
|
|
10
10
|
useOnCollaboratorLeave,
|
|
11
11
|
useOnPostSave,
|
|
12
12
|
} from './hooks/use-post-editor-awareness-state';
|
|
13
|
+
import { getTemplateInfo } from './utils/get-template-info';
|
|
14
|
+
import { getTemplatePartIcon } from './utils/get-template-part-icon';
|
|
15
|
+
import {
|
|
16
|
+
EntitiesSavedStatesExtensible,
|
|
17
|
+
default as EntitiesSavedStates,
|
|
18
|
+
} from './components/entities-saved-states';
|
|
19
|
+
import { useIsDirty as useEntitiesSavedStatesIsDirty } from './components/entities-saved-states/hooks/use-is-dirty';
|
|
13
20
|
import { lock } from './lock-unlock';
|
|
14
21
|
import { retrySyncConnection } from './sync';
|
|
15
22
|
import {
|
|
@@ -18,6 +25,11 @@ import {
|
|
|
18
25
|
} from './utils/crdt-user-selections';
|
|
19
26
|
|
|
20
27
|
const lockedApis = {
|
|
28
|
+
EntitiesSavedStates,
|
|
29
|
+
EntitiesSavedStatesExtensible,
|
|
30
|
+
getTemplateInfo,
|
|
31
|
+
getTemplatePartIcon,
|
|
32
|
+
useEntitiesSavedStatesIsDirty,
|
|
21
33
|
useEntityRecordsWithPermissions,
|
|
22
34
|
RECEIVE_INTERMEDIATE_RESULTS,
|
|
23
35
|
retrySyncConnection,
|
package/src/style.scss
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use "./components/entities-saved-states/style.scss" as *;
|