@elementor/editor-global-classes 3.33.0-117 → 3.33.0-119
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/dist/index.js +430 -316
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +394 -276
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -15
- package/src/api.ts +4 -0
- package/src/components/class-manager/class-manager-button.tsx +1 -1
- package/src/components/class-manager/class-manager-panel.tsx +1 -2
- package/src/components/class-manager/duplicate-label-dialog.tsx +159 -0
- package/src/components/search-and-filter/context.tsx +11 -1
- package/src/hooks/use-css-class-by-id.ts +8 -0
- package/src/init.ts +6 -6
- package/src/save-global-classes.tsx +50 -0
- package/src/store.ts +15 -0
- package/src/sync-with-document-save.ts +5 -4
- package/src/sync-with-document.tsx +20 -0
- package/src/components/class-manager/save-changes-dialog.tsx +0 -92
- package/src/save-global-classes.ts +0 -42
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-global-classes",
|
|
3
|
-
"version": "3.33.0-
|
|
3
|
+
"version": "3.33.0-119",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,22 +39,22 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "3.33.0-
|
|
43
|
-
"@elementor/editor-current-user": "3.33.0-
|
|
44
|
-
"@elementor/editor-documents": "3.33.0-
|
|
45
|
-
"@elementor/editor-editing-panel": "3.33.0-
|
|
46
|
-
"@elementor/editor-panels": "3.33.0-
|
|
47
|
-
"@elementor/editor-props": "3.33.0-
|
|
48
|
-
"@elementor/editor-styles": "3.33.0-
|
|
49
|
-
"@elementor/editor-styles-repository": "3.33.0-
|
|
50
|
-
"@elementor/editor-ui": "3.33.0-
|
|
51
|
-
"@elementor/editor-v1-adapters": "3.33.0-
|
|
52
|
-
"@elementor/http-client": "3.33.0-
|
|
42
|
+
"@elementor/editor": "3.33.0-119",
|
|
43
|
+
"@elementor/editor-current-user": "3.33.0-119",
|
|
44
|
+
"@elementor/editor-documents": "3.33.0-119",
|
|
45
|
+
"@elementor/editor-editing-panel": "3.33.0-119",
|
|
46
|
+
"@elementor/editor-panels": "3.33.0-119",
|
|
47
|
+
"@elementor/editor-props": "3.33.0-119",
|
|
48
|
+
"@elementor/editor-styles": "3.33.0-119",
|
|
49
|
+
"@elementor/editor-styles-repository": "3.33.0-119",
|
|
50
|
+
"@elementor/editor-ui": "3.33.0-119",
|
|
51
|
+
"@elementor/editor-v1-adapters": "3.33.0-119",
|
|
52
|
+
"@elementor/http-client": "3.33.0-119",
|
|
53
53
|
"@elementor/icons": "1.46.0",
|
|
54
|
-
"@elementor/query": "3.33.0-
|
|
55
|
-
"@elementor/store": "3.33.0-
|
|
54
|
+
"@elementor/query": "3.33.0-119",
|
|
55
|
+
"@elementor/store": "3.33.0-119",
|
|
56
56
|
"@elementor/ui": "1.36.12",
|
|
57
|
-
"@elementor/utils": "3.33.0-
|
|
57
|
+
"@elementor/utils": "3.33.0-119",
|
|
58
58
|
"@wordpress/i18n": "^5.13.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
package/src/api.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
__useActiveDocumentActions as useActiveDocumentActions,
|
|
5
5
|
} from '@elementor/editor-documents';
|
|
6
6
|
import { useUserStylesCapability } from '@elementor/editor-styles-repository';
|
|
7
|
+
import { SaveChangesDialog, useDialog } from '@elementor/editor-ui';
|
|
7
8
|
import { IconButton, Tooltip } from '@elementor/ui';
|
|
8
9
|
import { __ } from '@wordpress/i18n';
|
|
9
10
|
|
|
@@ -11,7 +12,6 @@ import { globalClassesStylesProvider } from '../../global-classes-styles-provide
|
|
|
11
12
|
import { usePrefetchCssClassUsage } from '../../hooks/use-prefetch-css-class-usage';
|
|
12
13
|
import { usePanelActions } from './class-manager-panel';
|
|
13
14
|
import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
|
|
14
|
-
import { SaveChangesDialog, useDialog } from './save-changes-dialog';
|
|
15
15
|
|
|
16
16
|
export const ClassManagerButton = () => {
|
|
17
17
|
const document = useActiveDocument();
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
PanelHeader,
|
|
10
10
|
PanelHeaderTitle,
|
|
11
11
|
} from '@elementor/editor-panels';
|
|
12
|
-
import { ThemeProvider } from '@elementor/editor-ui';
|
|
12
|
+
import { SaveChangesDialog, ThemeProvider, useDialog } from '@elementor/editor-ui';
|
|
13
13
|
import { changeEditMode } from '@elementor/editor-v1-adapters';
|
|
14
14
|
import { XIcon } from '@elementor/icons';
|
|
15
15
|
import { useMutation } from '@elementor/query';
|
|
@@ -42,7 +42,6 @@ import { hasDeletedItems, onDelete } from './delete-class';
|
|
|
42
42
|
import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
|
|
43
43
|
import { GlobalClassesList } from './global-classes-list';
|
|
44
44
|
import { blockPanelInteractions, unblockPanelInteractions } from './panel-interactions';
|
|
45
|
-
import { SaveChangesDialog, useDialog } from './save-changes-dialog';
|
|
46
45
|
|
|
47
46
|
const id = 'global-classes-manager';
|
|
48
47
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { closeDialog, EllipsisWithTooltip } from '@elementor/editor-ui';
|
|
3
|
+
import { InfoCircleFilledIcon } from '@elementor/icons';
|
|
4
|
+
import {
|
|
5
|
+
Alert,
|
|
6
|
+
Box,
|
|
7
|
+
Button,
|
|
8
|
+
DialogActions,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
Divider,
|
|
12
|
+
Icon,
|
|
13
|
+
Stack,
|
|
14
|
+
Typography,
|
|
15
|
+
} from '@elementor/ui';
|
|
16
|
+
import { __ } from '@wordpress/i18n';
|
|
17
|
+
|
|
18
|
+
import { type ModifiedLabels } from '../../store';
|
|
19
|
+
|
|
20
|
+
const DUP_PREFIX = 'DUP_';
|
|
21
|
+
|
|
22
|
+
export const DuplicateLabelDialog = ( {
|
|
23
|
+
modifiedLabels,
|
|
24
|
+
onApprove,
|
|
25
|
+
}: {
|
|
26
|
+
modifiedLabels: ModifiedLabels;
|
|
27
|
+
onApprove?: () => void;
|
|
28
|
+
} ) => {
|
|
29
|
+
const handleButtonClick = () => {
|
|
30
|
+
localStorage.setItem( 'elementor-global-classes-search', DUP_PREFIX );
|
|
31
|
+
onApprove?.();
|
|
32
|
+
closeDialog();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<DialogHeader logo={ false }>
|
|
38
|
+
<Box display="flex" alignItems="center" gap={ 1 }>
|
|
39
|
+
<Icon color="secondary">
|
|
40
|
+
<InfoCircleFilledIcon fontSize="medium" />
|
|
41
|
+
</Icon>
|
|
42
|
+
<Typography variant="subtitle1">
|
|
43
|
+
{ __( "We've published your page and updated class names.", 'elementor' ) }
|
|
44
|
+
</Typography>
|
|
45
|
+
</Box>
|
|
46
|
+
</DialogHeader>
|
|
47
|
+
<DialogContent>
|
|
48
|
+
<Stack spacing={ 2 } direction="column">
|
|
49
|
+
<Typography variant="body2">
|
|
50
|
+
{ __(
|
|
51
|
+
'Some new classes used the same names as existing ones. To prevent conflicts, we added the prefix',
|
|
52
|
+
'elementor'
|
|
53
|
+
) }
|
|
54
|
+
<strong> { DUP_PREFIX }</strong>
|
|
55
|
+
</Typography>
|
|
56
|
+
|
|
57
|
+
<Box>
|
|
58
|
+
<Box
|
|
59
|
+
sx={ {
|
|
60
|
+
width: '100%',
|
|
61
|
+
display: 'flex',
|
|
62
|
+
gap: 2,
|
|
63
|
+
alignItems: 'flex-start',
|
|
64
|
+
} }
|
|
65
|
+
>
|
|
66
|
+
<Typography
|
|
67
|
+
variant="subtitle2"
|
|
68
|
+
sx={ {
|
|
69
|
+
fontWeight: 'bold',
|
|
70
|
+
flex: 1,
|
|
71
|
+
flexShrink: 1,
|
|
72
|
+
flexGrow: 1,
|
|
73
|
+
minWidth: 0,
|
|
74
|
+
} }
|
|
75
|
+
>
|
|
76
|
+
{ __( 'Before', 'elementor' ) }
|
|
77
|
+
</Typography>
|
|
78
|
+
<Typography
|
|
79
|
+
variant="subtitle2"
|
|
80
|
+
sx={ {
|
|
81
|
+
minWidth: '200px',
|
|
82
|
+
fontWeight: 'bold',
|
|
83
|
+
flexShrink: 0,
|
|
84
|
+
flexGrow: 0,
|
|
85
|
+
width: '200px',
|
|
86
|
+
maxWidth: '200px',
|
|
87
|
+
} }
|
|
88
|
+
>
|
|
89
|
+
{ __( 'After', 'elementor' ) }
|
|
90
|
+
</Typography>
|
|
91
|
+
</Box>
|
|
92
|
+
<Divider sx={ { mt: 0.5, mb: 0.5 } } />
|
|
93
|
+
<Stack direction="column" gap={ 0.5 } sx={ { pb: 2 } }>
|
|
94
|
+
{ Object.values( modifiedLabels ).map( ( { original, modified }, index ) => (
|
|
95
|
+
<Box
|
|
96
|
+
key={ index }
|
|
97
|
+
sx={ {
|
|
98
|
+
width: '100%',
|
|
99
|
+
display: 'flex',
|
|
100
|
+
gap: 2,
|
|
101
|
+
alignItems: 'flex-start',
|
|
102
|
+
} }
|
|
103
|
+
>
|
|
104
|
+
<Box
|
|
105
|
+
sx={ {
|
|
106
|
+
flex: 1,
|
|
107
|
+
flexShrink: 1,
|
|
108
|
+
flexGrow: 1,
|
|
109
|
+
minWidth: 0,
|
|
110
|
+
} }
|
|
111
|
+
>
|
|
112
|
+
<EllipsisWithTooltip title={ original }>
|
|
113
|
+
<Typography variant="body2" sx={ { color: 'text.secondary' } }>
|
|
114
|
+
{ original }
|
|
115
|
+
</Typography>
|
|
116
|
+
</EllipsisWithTooltip>
|
|
117
|
+
</Box>
|
|
118
|
+
<Box
|
|
119
|
+
sx={ {
|
|
120
|
+
minWidth: '200px',
|
|
121
|
+
flexShrink: 0,
|
|
122
|
+
flexGrow: 0,
|
|
123
|
+
width: '200px',
|
|
124
|
+
maxWidth: '200px',
|
|
125
|
+
} }
|
|
126
|
+
>
|
|
127
|
+
<EllipsisWithTooltip title={ modified }>
|
|
128
|
+
<Typography variant="body2" sx={ { color: 'text.primary' } }>
|
|
129
|
+
{ modified }
|
|
130
|
+
</Typography>
|
|
131
|
+
</EllipsisWithTooltip>
|
|
132
|
+
</Box>
|
|
133
|
+
</Box>
|
|
134
|
+
) ) }
|
|
135
|
+
</Stack>
|
|
136
|
+
<Box>
|
|
137
|
+
<Alert severity="info" size="small" color="secondary">
|
|
138
|
+
<strong>{ __( 'Your designs and classes are safe.', 'elementor' ) }</strong>
|
|
139
|
+
{ __(
|
|
140
|
+
'Only the prefixes were added.Find them in Class Manager by searching',
|
|
141
|
+
'elementor'
|
|
142
|
+
) }
|
|
143
|
+
<strong>{ DUP_PREFIX }</strong>
|
|
144
|
+
</Alert>
|
|
145
|
+
</Box>
|
|
146
|
+
</Box>
|
|
147
|
+
</Stack>
|
|
148
|
+
</DialogContent>
|
|
149
|
+
<DialogActions>
|
|
150
|
+
<Button color="secondary" variant="text" onClick={ handleButtonClick }>
|
|
151
|
+
{ __( 'Go to Class Manager', 'elementor' ) }
|
|
152
|
+
</Button>
|
|
153
|
+
<Button color="secondary" variant="contained" onClick={ closeDialog }>
|
|
154
|
+
{ __( 'Done', 'elementor' ) }
|
|
155
|
+
</Button>
|
|
156
|
+
</DialogActions>
|
|
157
|
+
</>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
@@ -35,9 +35,19 @@ const INIT_CHECKED_FILTERS: CheckedFilters = {
|
|
|
35
35
|
|
|
36
36
|
export const SearchAndFilterProvider = ( { children }: React.PropsWithChildren ) => {
|
|
37
37
|
const [ filters, setFilters ] = React.useState< CheckedFilters >( INIT_CHECKED_FILTERS );
|
|
38
|
+
|
|
39
|
+
const getInitialSearchValue = () => {
|
|
40
|
+
const storedValue = localStorage.getItem( 'elementor-global-classes-search' );
|
|
41
|
+
if ( storedValue ) {
|
|
42
|
+
localStorage.removeItem( 'elementor-global-classes-search' );
|
|
43
|
+
return storedValue;
|
|
44
|
+
}
|
|
45
|
+
return '';
|
|
46
|
+
};
|
|
47
|
+
|
|
38
48
|
const { debouncedValue, inputValue, handleChange } = useDebounceState( {
|
|
39
49
|
delay: 300,
|
|
40
|
-
initialValue:
|
|
50
|
+
initialValue: getInitialSearchValue(),
|
|
41
51
|
} );
|
|
42
52
|
|
|
43
53
|
const onClearSearch = () => {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
2
|
+
import { __useSelector as useSelector, type SliceState } from '@elementor/store';
|
|
3
|
+
|
|
4
|
+
import { selectClass, type slice } from '../store';
|
|
5
|
+
|
|
6
|
+
export const useCssClassById = ( id: StyleDefinitionID ) => {
|
|
7
|
+
return useSelector( ( state: SliceState< typeof slice > ) => selectClass( state, id ) );
|
|
8
|
+
};
|
package/src/init.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from '@elementor/editor-editing-panel';
|
|
7
7
|
import { __registerPanel as registerPanel } from '@elementor/editor-panels';
|
|
8
8
|
import { stylesRepository } from '@elementor/editor-styles-repository';
|
|
9
|
-
import { __privateListenTo as listenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
|
|
10
9
|
import { __registerSlice as registerSlice } from '@elementor/store';
|
|
11
10
|
|
|
12
11
|
import { ClassManagerButton } from './components/class-manager/class-manager-button';
|
|
@@ -15,7 +14,7 @@ import { ConvertLocalClassToGlobalClass } from './components/convert-local-class
|
|
|
15
14
|
import { PopulateStore } from './components/populate-store';
|
|
16
15
|
import { GLOBAL_CLASSES_PROVIDER_KEY, globalClassesStylesProvider } from './global-classes-styles-provider';
|
|
17
16
|
import { slice } from './store';
|
|
18
|
-
import {
|
|
17
|
+
import { SyncWithDocumentSave } from './sync-with-document';
|
|
19
18
|
|
|
20
19
|
export function init() {
|
|
21
20
|
registerSlice( slice );
|
|
@@ -28,6 +27,11 @@ export function init() {
|
|
|
28
27
|
component: PopulateStore,
|
|
29
28
|
} );
|
|
30
29
|
|
|
30
|
+
injectIntoLogic( {
|
|
31
|
+
id: 'global-classes-sync-with-document',
|
|
32
|
+
component: SyncWithDocumentSave,
|
|
33
|
+
} );
|
|
34
|
+
|
|
31
35
|
injectIntoCssClassConvert( {
|
|
32
36
|
id: 'global-classes-convert-from-local-class',
|
|
33
37
|
component: ConvertLocalClassToGlobalClass,
|
|
@@ -42,8 +46,4 @@ export function init() {
|
|
|
42
46
|
name: 'global',
|
|
43
47
|
getThemeColor: ( theme ) => theme.palette.global.dark,
|
|
44
48
|
} );
|
|
45
|
-
|
|
46
|
-
listenTo( v1ReadyEvent(), () => {
|
|
47
|
-
syncWithDocumentSave();
|
|
48
|
-
} );
|
|
49
49
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { openDialog } from '@elementor/editor-ui';
|
|
3
|
+
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
4
|
+
import { hash } from '@elementor/utils';
|
|
5
|
+
|
|
6
|
+
import { API_ERROR_CODES, apiClient, type ApiContext } from './api';
|
|
7
|
+
import { DuplicateLabelDialog } from './components/class-manager/duplicate-label-dialog';
|
|
8
|
+
import { type GlobalClasses, selectData, selectFrontendInitialData, selectPreviewInitialData, slice } from './store';
|
|
9
|
+
|
|
10
|
+
type Options = {
|
|
11
|
+
context: ApiContext;
|
|
12
|
+
onApprove?: () => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function saveGlobalClasses( { context, onApprove }: Options ) {
|
|
16
|
+
const state = selectData( getState() );
|
|
17
|
+
const apiAction = context === 'preview' ? apiClient.saveDraft : apiClient.publish;
|
|
18
|
+
const currentContext = context === 'preview' ? selectPreviewInitialData : selectFrontendInitialData;
|
|
19
|
+
const response = await apiAction( {
|
|
20
|
+
items: state.items,
|
|
21
|
+
order: state.order,
|
|
22
|
+
changes: calculateChanges( state, currentContext( getState() ) ),
|
|
23
|
+
} );
|
|
24
|
+
|
|
25
|
+
dispatch( slice.actions.reset( { context } ) );
|
|
26
|
+
if ( response?.data?.data?.code === API_ERROR_CODES.DUPLICATED_LABEL ) {
|
|
27
|
+
dispatch( slice.actions.updateMultiple( response.data.data.modifiedLabels ) );
|
|
28
|
+
openDialog( {
|
|
29
|
+
component: (
|
|
30
|
+
<DuplicateLabelDialog
|
|
31
|
+
modifiedLabels={ response.data.data.modifiedLabels || [] }
|
|
32
|
+
onApprove={ onApprove }
|
|
33
|
+
/>
|
|
34
|
+
),
|
|
35
|
+
} );
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function calculateChanges( state: GlobalClasses, initialData: GlobalClasses ) {
|
|
40
|
+
const stateIds = Object.keys( state.items );
|
|
41
|
+
const initialDataIds = Object.keys( initialData.items );
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
added: stateIds.filter( ( id ) => ! initialDataIds.includes( id ) ),
|
|
45
|
+
deleted: initialDataIds.filter( ( id ) => ! stateIds.includes( id ) ),
|
|
46
|
+
modified: stateIds.filter( ( id ) => {
|
|
47
|
+
return id in initialData.items && hash( state.items[ id ] ) !== hash( initialData.items[ id ] );
|
|
48
|
+
} ),
|
|
49
|
+
};
|
|
50
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -32,6 +32,13 @@ type GlobalClassesState = {
|
|
|
32
32
|
isDirty: boolean;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
export type ModifiedLabels = {
|
|
36
|
+
[ id: string ]: {
|
|
37
|
+
original: string;
|
|
38
|
+
modified: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
35
42
|
const localHistory = SnapshotHistory.get< GlobalClasses >( 'global-classes' );
|
|
36
43
|
|
|
37
44
|
const initialState: GlobalClassesState = {
|
|
@@ -108,6 +115,14 @@ export const slice = createSlice( {
|
|
|
108
115
|
state.isDirty = true;
|
|
109
116
|
},
|
|
110
117
|
|
|
118
|
+
updateMultiple( state, { payload }: PayloadAction< ModifiedLabels > ) {
|
|
119
|
+
localHistory.next( state.data );
|
|
120
|
+
Object.entries( payload ).forEach( ( [ id, { modified } ] ) => {
|
|
121
|
+
state.data.items[ id ].label = modified;
|
|
122
|
+
} );
|
|
123
|
+
|
|
124
|
+
state.isDirty = false;
|
|
125
|
+
},
|
|
111
126
|
updateProps(
|
|
112
127
|
state,
|
|
113
128
|
{
|
|
@@ -7,10 +7,10 @@ import { UPDATE_CLASS_CAPABILITY_KEY } from './capabilities';
|
|
|
7
7
|
import { saveGlobalClasses } from './save-global-classes';
|
|
8
8
|
import { selectIsDirty } from './store';
|
|
9
9
|
|
|
10
|
-
export function syncWithDocumentSave() {
|
|
10
|
+
export function syncWithDocumentSave( panelActions?: { open: () => void } ) {
|
|
11
11
|
const unsubscribe = syncDirtyState();
|
|
12
12
|
|
|
13
|
-
bindSaveAction();
|
|
13
|
+
bindSaveAction( panelActions );
|
|
14
14
|
|
|
15
15
|
return unsubscribe;
|
|
16
16
|
}
|
|
@@ -25,7 +25,7 @@ function syncDirtyState() {
|
|
|
25
25
|
} );
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function bindSaveAction() {
|
|
28
|
+
function bindSaveAction( panelActions?: { open: () => void } ) {
|
|
29
29
|
registerDataHook( 'after', 'document/save/save', ( args ) => {
|
|
30
30
|
const user = getCurrentUser();
|
|
31
31
|
|
|
@@ -35,8 +35,9 @@ function bindSaveAction() {
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
saveGlobalClasses( {
|
|
39
39
|
context: args.status === 'publish' ? 'frontend' : 'preview',
|
|
40
|
+
onApprove: panelActions?.open,
|
|
40
41
|
} );
|
|
41
42
|
} );
|
|
42
43
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { __privateListenTo as listenTo, v1ReadyEvent } from '@elementor/editor-v1-adapters';
|
|
3
|
+
|
|
4
|
+
import { usePanelActions } from './components/class-manager/class-manager-panel';
|
|
5
|
+
import { syncWithDocumentSave } from './sync-with-document-save';
|
|
6
|
+
|
|
7
|
+
export function SyncWithDocumentSave() {
|
|
8
|
+
const panelActions = usePanelActions();
|
|
9
|
+
|
|
10
|
+
useEffect( () => {
|
|
11
|
+
listenTo( v1ReadyEvent(), () => {
|
|
12
|
+
syncWithDocumentSave( panelActions );
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line react-compiler/react-compiler
|
|
16
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
17
|
+
}, [] );
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { AlertTriangleFilledIcon } from '@elementor/icons';
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
Dialog,
|
|
7
|
-
DialogActions,
|
|
8
|
-
DialogContent,
|
|
9
|
-
DialogContentText,
|
|
10
|
-
type DialogContentTextProps,
|
|
11
|
-
type DialogProps,
|
|
12
|
-
DialogTitle,
|
|
13
|
-
} from '@elementor/ui';
|
|
14
|
-
|
|
15
|
-
const TITLE_ID = 'save-changes-dialog';
|
|
16
|
-
|
|
17
|
-
const SaveChangesDialog = ( { children, onClose }: Pick< DialogProps, 'children' | 'onClose' > ) => (
|
|
18
|
-
<Dialog open onClose={ onClose } aria-labelledby={ TITLE_ID } maxWidth="xs">
|
|
19
|
-
{ children }
|
|
20
|
-
</Dialog>
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const SaveChangesDialogTitle = ( { children }: React.PropsWithChildren ) => (
|
|
24
|
-
<DialogTitle id={ TITLE_ID } display="flex" alignItems="center" gap={ 1 } sx={ { lineHeight: 1 } }>
|
|
25
|
-
<AlertTriangleFilledIcon color="secondary" />
|
|
26
|
-
{ children }
|
|
27
|
-
</DialogTitle>
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const SaveChangesDialogContent = ( { children }: React.PropsWithChildren ) => (
|
|
31
|
-
<DialogContent>{ children }</DialogContent>
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const SaveChangesDialogContentText = ( props: DialogContentTextProps ) => (
|
|
35
|
-
<DialogContentText variant="body2" color="textPrimary" display="flex" flexDirection="column" { ...props } />
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
type Action = {
|
|
39
|
-
label: string;
|
|
40
|
-
action: () => void | Promise< void >;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
type ConfirmationDialogActionsProps = {
|
|
44
|
-
actions: {
|
|
45
|
-
cancel?: Action;
|
|
46
|
-
confirm: Action;
|
|
47
|
-
discard?: Action;
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
const SaveChangesDialogActions = ( { actions }: ConfirmationDialogActionsProps ) => {
|
|
51
|
-
const [ isConfirming, setIsConfirming ] = useState( false );
|
|
52
|
-
const { cancel, confirm, discard } = actions;
|
|
53
|
-
|
|
54
|
-
const onConfirm = async () => {
|
|
55
|
-
setIsConfirming( true );
|
|
56
|
-
await confirm.action();
|
|
57
|
-
setIsConfirming( false );
|
|
58
|
-
};
|
|
59
|
-
return (
|
|
60
|
-
<DialogActions>
|
|
61
|
-
{ cancel && (
|
|
62
|
-
<Button variant="text" color="secondary" onClick={ cancel.action }>
|
|
63
|
-
{ cancel.label }
|
|
64
|
-
</Button>
|
|
65
|
-
) }
|
|
66
|
-
{ discard && (
|
|
67
|
-
<Button variant="text" color="secondary" onClick={ discard.action }>
|
|
68
|
-
{ discard.label }
|
|
69
|
-
</Button>
|
|
70
|
-
) }
|
|
71
|
-
<Button variant="contained" color="secondary" onClick={ onConfirm } loading={ isConfirming }>
|
|
72
|
-
{ confirm.label }
|
|
73
|
-
</Button>
|
|
74
|
-
</DialogActions>
|
|
75
|
-
);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
SaveChangesDialog.Title = SaveChangesDialogTitle;
|
|
79
|
-
SaveChangesDialog.Content = SaveChangesDialogContent;
|
|
80
|
-
SaveChangesDialog.ContentText = SaveChangesDialogContentText;
|
|
81
|
-
SaveChangesDialog.Actions = SaveChangesDialogActions;
|
|
82
|
-
|
|
83
|
-
const useDialog = () => {
|
|
84
|
-
const [ isOpen, setIsOpen ] = useState( false );
|
|
85
|
-
|
|
86
|
-
const open = () => setIsOpen( true );
|
|
87
|
-
const close = () => setIsOpen( false );
|
|
88
|
-
|
|
89
|
-
return { isOpen, open, close };
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export { SaveChangesDialog, useDialog };
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
2
|
-
import { hash } from '@elementor/utils';
|
|
3
|
-
|
|
4
|
-
import { apiClient, type ApiContext } from './api';
|
|
5
|
-
import { type GlobalClasses, selectData, selectFrontendInitialData, selectPreviewInitialData, slice } from './store';
|
|
6
|
-
|
|
7
|
-
type Options = {
|
|
8
|
-
context: ApiContext;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export async function saveGlobalClasses( { context }: Options ) {
|
|
12
|
-
const state = selectData( getState() );
|
|
13
|
-
|
|
14
|
-
if ( context === 'preview' ) {
|
|
15
|
-
await apiClient.saveDraft( {
|
|
16
|
-
items: state.items,
|
|
17
|
-
order: state.order,
|
|
18
|
-
changes: calculateChanges( state, selectPreviewInitialData( getState() ) ),
|
|
19
|
-
} );
|
|
20
|
-
} else {
|
|
21
|
-
await apiClient.publish( {
|
|
22
|
-
items: state.items,
|
|
23
|
-
order: state.order,
|
|
24
|
-
changes: calculateChanges( state, selectFrontendInitialData( getState() ) ),
|
|
25
|
-
} );
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
dispatch( slice.actions.reset( { context } ) );
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function calculateChanges( state: GlobalClasses, initialData: GlobalClasses ) {
|
|
32
|
-
const stateIds = Object.keys( state.items );
|
|
33
|
-
const initialDataIds = Object.keys( initialData.items );
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
added: stateIds.filter( ( id ) => ! initialDataIds.includes( id ) ),
|
|
37
|
-
deleted: initialDataIds.filter( ( id ) => ! stateIds.includes( id ) ),
|
|
38
|
-
modified: stateIds.filter( ( id ) => {
|
|
39
|
-
return id in initialData.items && hash( state.items[ id ] ) !== hash( initialData.items[ id ] );
|
|
40
|
-
} ),
|
|
41
|
-
};
|
|
42
|
-
}
|