@elementor/editor-global-classes 4.1.0-830 → 4.1.0-832
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.d.mts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +279 -127
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +260 -111
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -20
- package/src/api.ts +32 -15
- package/src/components/global-styles-import-listener.tsx +8 -33
- package/src/components/populate-store.tsx +10 -24
- package/src/global-classes-styles-provider.ts +38 -12
- package/src/index.ts +5 -0
- package/src/load-document-classes.ts +76 -0
- package/src/load-existing-classes.ts +49 -0
- package/src/mcp-integration/classes-resource.ts +3 -1
- package/src/save-global-classes.tsx +17 -2
- package/src/store.ts +63 -4
- package/src/utils/create-labels-for-classes.ts +7 -0
- package/src/utils/tracking.ts +14 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-global-classes",
|
|
3
|
-
"version": "4.1.0-
|
|
3
|
+
"version": "4.1.0-832",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -39,29 +39,29 @@
|
|
|
39
39
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@elementor/editor": "4.1.0-
|
|
43
|
-
"@elementor/editor-current-user": "4.1.0-
|
|
44
|
-
"@elementor/editor-documents": "4.1.0-
|
|
45
|
-
"@elementor/editor-editing-panel": "4.1.0-
|
|
46
|
-
"@elementor/editor-mcp": "4.1.0-
|
|
47
|
-
"@elementor/editor-panels": "4.1.0-
|
|
48
|
-
"@elementor/editor-props": "4.1.0-
|
|
49
|
-
"@elementor/editor-variables": "4.1.0-
|
|
50
|
-
"@elementor/editor-styles": "4.1.0-
|
|
51
|
-
"@elementor/editor-canvas": "4.1.0-
|
|
52
|
-
"@elementor/editor-styles-repository": "4.1.0-
|
|
53
|
-
"@elementor/editor-ui": "4.1.0-
|
|
54
|
-
"@elementor/editor-v1-adapters": "4.1.0-
|
|
55
|
-
"@elementor/http-client": "4.1.0-
|
|
42
|
+
"@elementor/editor": "4.1.0-832",
|
|
43
|
+
"@elementor/editor-current-user": "4.1.0-832",
|
|
44
|
+
"@elementor/editor-documents": "4.1.0-832",
|
|
45
|
+
"@elementor/editor-editing-panel": "4.1.0-832",
|
|
46
|
+
"@elementor/editor-mcp": "4.1.0-832",
|
|
47
|
+
"@elementor/editor-panels": "4.1.0-832",
|
|
48
|
+
"@elementor/editor-props": "4.1.0-832",
|
|
49
|
+
"@elementor/editor-variables": "4.1.0-832",
|
|
50
|
+
"@elementor/editor-styles": "4.1.0-832",
|
|
51
|
+
"@elementor/editor-canvas": "4.1.0-832",
|
|
52
|
+
"@elementor/editor-styles-repository": "4.1.0-832",
|
|
53
|
+
"@elementor/editor-ui": "4.1.0-832",
|
|
54
|
+
"@elementor/editor-v1-adapters": "4.1.0-832",
|
|
55
|
+
"@elementor/http-client": "4.1.0-832",
|
|
56
56
|
"@elementor/icons": "^1.68.0",
|
|
57
|
-
"@elementor/query": "4.1.0-
|
|
58
|
-
"@elementor/schema": "4.1.0-
|
|
59
|
-
"@elementor/store": "4.1.0-
|
|
57
|
+
"@elementor/query": "4.1.0-832",
|
|
58
|
+
"@elementor/schema": "4.1.0-832",
|
|
59
|
+
"@elementor/store": "4.1.0-832",
|
|
60
60
|
"@elementor/ui": "1.37.5",
|
|
61
|
-
"@elementor/utils": "4.1.0-
|
|
61
|
+
"@elementor/utils": "4.1.0-832",
|
|
62
62
|
"@tanstack/react-virtual": "^3.13.24",
|
|
63
63
|
"@wordpress/i18n": "^5.13.0",
|
|
64
|
-
"@elementor/events": "4.1.0-
|
|
64
|
+
"@elementor/events": "4.1.0-832"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"react": "^18.3.1",
|
package/src/api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type StyleDefinition, type StyleDefinitionID
|
|
1
|
+
import { type StyleDefinition, type StyleDefinitionID } from '@elementor/editor-styles';
|
|
2
2
|
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
3
3
|
|
|
4
4
|
import { type CssClassUsage } from './components/css-class-usage/types';
|
|
@@ -7,13 +7,24 @@ import { type GlobalClasses } from './store';
|
|
|
7
7
|
const RESOURCE_URL = '/global-classes';
|
|
8
8
|
const BASE_URL = 'elementor/v1';
|
|
9
9
|
const RESOURCE_USAGE_URL = `${ RESOURCE_URL }/usage`;
|
|
10
|
+
const RESOURCE_POST_URL = `${ RESOURCE_URL }/post`;
|
|
11
|
+
const RESOURCE_STYLES_URL = `${ RESOURCE_URL }/styles`;
|
|
10
12
|
|
|
11
13
|
type GlobalClassesUsageResponse = HttpResponse< CssClassUsage >;
|
|
12
14
|
|
|
13
|
-
export type
|
|
14
|
-
|
|
15
|
+
export type GlobalClassIndexEntry = {
|
|
16
|
+
id: StyleDefinitionID;
|
|
17
|
+
label: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type GlobalClassesIndexHttpResponse = HttpResponse< GlobalClassIndexEntry[], Record< string, never > >;
|
|
21
|
+
|
|
22
|
+
export type StyleDefinitionsNullableMap = Record< StyleDefinitionID, StyleDefinition | null >;
|
|
23
|
+
|
|
24
|
+
export type GlobalClassesStylesHttpResponse = HttpResponse<
|
|
25
|
+
StyleDefinitionsNullableMap,
|
|
15
26
|
{
|
|
16
|
-
order:
|
|
27
|
+
order: StyleDefinitionID[];
|
|
17
28
|
}
|
|
18
29
|
>;
|
|
19
30
|
|
|
@@ -27,27 +38,33 @@ type UpdatePayload = GlobalClasses & {
|
|
|
27
38
|
|
|
28
39
|
export type ApiContext = 'preview' | 'frontend';
|
|
29
40
|
|
|
41
|
+
function saveGlobalClasses( context: ApiContext, payload: UpdatePayload ) {
|
|
42
|
+
return httpService().put( `${ BASE_URL }${ RESOURCE_URL }`, payload, {
|
|
43
|
+
params: { context },
|
|
44
|
+
} );
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export const apiClient = {
|
|
31
48
|
usage: () => httpService().get< GlobalClassesUsageResponse >( `${ BASE_URL }${ RESOURCE_USAGE_URL }` ),
|
|
32
49
|
|
|
33
50
|
all: ( context: ApiContext = 'preview' ) =>
|
|
34
|
-
httpService().get<
|
|
51
|
+
httpService().get< GlobalClassesIndexHttpResponse >( `${ BASE_URL }${ RESOURCE_URL }`, {
|
|
35
52
|
params: { context },
|
|
36
53
|
} ),
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
httpService().
|
|
40
|
-
params: {
|
|
41
|
-
context: 'frontend' satisfies ApiContext,
|
|
42
|
-
},
|
|
55
|
+
getStylesForPost: ( postId: number, context: ApiContext = 'preview' ) =>
|
|
56
|
+
httpService().get< GlobalClassesStylesHttpResponse >( `${ BASE_URL }${ RESOURCE_POST_URL }`, {
|
|
57
|
+
params: { context, post_id: postId },
|
|
43
58
|
} ),
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
httpService().
|
|
47
|
-
params: {
|
|
48
|
-
context: 'preview' satisfies ApiContext,
|
|
49
|
-
},
|
|
60
|
+
getStylesByIds: ( ids: StyleDefinitionID[], context: ApiContext = 'preview' ) =>
|
|
61
|
+
httpService().get< GlobalClassesStylesHttpResponse >( `${ BASE_URL }${ RESOURCE_STYLES_URL }`, {
|
|
62
|
+
params: { context, ids: ids.join( ',' ) },
|
|
50
63
|
} ),
|
|
64
|
+
|
|
65
|
+
publish: ( payload: UpdatePayload ) => saveGlobalClasses( 'frontend', payload ),
|
|
66
|
+
|
|
67
|
+
saveDraft: ( payload: UpdatePayload ) => saveGlobalClasses( 'preview', payload ),
|
|
51
68
|
};
|
|
52
69
|
|
|
53
70
|
export const API_ERROR_CODES = {
|
|
@@ -1,51 +1,26 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import { GLOBAL_STYLES_IMPORTED_EVENT } from '@elementor/editor-canvas';
|
|
2
|
+
import { GLOBAL_STYLES_IMPORTED_EVENT, type ImportedGlobalStylesPayload } from '@elementor/editor-canvas';
|
|
3
3
|
import { __useDispatch as useDispatch } from '@elementor/store';
|
|
4
4
|
|
|
5
|
-
import { apiClient } from '../api';
|
|
6
5
|
import { slice } from '../store';
|
|
7
6
|
|
|
8
7
|
export function GlobalStylesImportListener() {
|
|
9
8
|
const dispatch = useDispatch();
|
|
10
9
|
|
|
11
10
|
useEffect( () => {
|
|
12
|
-
const handleGlobalStylesImported = ( event: CustomEvent ) => {
|
|
13
|
-
const importedClasses = event.detail?.global_classes;
|
|
11
|
+
const handleGlobalStylesImported = ( event: CustomEvent< ImportedGlobalStylesPayload > ) => {
|
|
12
|
+
const importedClasses = event.detail?.global_classes as ImportedGlobalStylesPayload[ 'global_classes' ];
|
|
14
13
|
|
|
15
14
|
if ( importedClasses?.items && importedClasses?.order ) {
|
|
15
|
+
const { items } = importedClasses;
|
|
16
|
+
|
|
16
17
|
dispatch(
|
|
17
|
-
slice.actions.
|
|
18
|
-
preview:
|
|
19
|
-
|
|
20
|
-
order: importedClasses.order,
|
|
21
|
-
},
|
|
22
|
-
frontend: {
|
|
23
|
-
items: importedClasses.items,
|
|
24
|
-
order: importedClasses.order,
|
|
25
|
-
},
|
|
18
|
+
slice.actions.mergeExistingClasses( {
|
|
19
|
+
preview: items,
|
|
20
|
+
frontend: items,
|
|
26
21
|
} )
|
|
27
22
|
);
|
|
28
23
|
}
|
|
29
|
-
|
|
30
|
-
Promise.all( [ apiClient.all( 'preview' ), apiClient.all( 'frontend' ) ] )
|
|
31
|
-
.then( ( [ previewRes, frontendRes ] ) => {
|
|
32
|
-
const { data: previewData } = previewRes;
|
|
33
|
-
const { data: frontendData } = frontendRes;
|
|
34
|
-
|
|
35
|
-
dispatch(
|
|
36
|
-
slice.actions.load( {
|
|
37
|
-
preview: {
|
|
38
|
-
items: previewData.data,
|
|
39
|
-
order: previewData.meta.order,
|
|
40
|
-
},
|
|
41
|
-
frontend: {
|
|
42
|
-
items: frontendData.data,
|
|
43
|
-
order: frontendData.meta.order,
|
|
44
|
-
},
|
|
45
|
-
} )
|
|
46
|
-
);
|
|
47
|
-
} )
|
|
48
|
-
.catch( () => {} );
|
|
49
24
|
};
|
|
50
25
|
|
|
51
26
|
window.addEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported as EventListener );
|
|
@@ -1,33 +1,19 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { registerDataHook } from '@elementor/editor-v1-adapters';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { slice } from '../store';
|
|
4
|
+
import { loadCurrentDocumentClasses } from '../load-document-classes';
|
|
6
5
|
|
|
7
6
|
export function PopulateStore() {
|
|
8
|
-
const dispatch = useDispatch();
|
|
9
|
-
|
|
10
7
|
useEffect( () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
// TODO - we run it early to have the labels mapping prior to the canvas rendering
|
|
9
|
+
// but in fact we need a way to re-render any dependant twig-templated widgets/elements once we get the initial data
|
|
10
|
+
// in case the canvas rendering has occurred prior to the resolving of this fetch
|
|
11
|
+
loadCurrentDocumentClasses();
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
order: previewData.meta.order,
|
|
21
|
-
},
|
|
22
|
-
frontend: {
|
|
23
|
-
items: frontendData.data,
|
|
24
|
-
order: frontendData.meta.order,
|
|
25
|
-
},
|
|
26
|
-
} )
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
}, [ dispatch ] );
|
|
13
|
+
registerDataHook( 'after', 'editor/documents/attach-preview', async () => {
|
|
14
|
+
await loadCurrentDocumentClasses();
|
|
15
|
+
} );
|
|
16
|
+
}, [] );
|
|
31
17
|
|
|
32
18
|
return null;
|
|
33
19
|
}
|
|
@@ -14,20 +14,23 @@ import { __ } from '@wordpress/i18n';
|
|
|
14
14
|
|
|
15
15
|
import { getCapabilities } from './capabilities';
|
|
16
16
|
import { GlobalClassLabelAlreadyExistsError, GlobalClassTrackingError } from './errors';
|
|
17
|
+
import { loadExistingClasses } from './load-existing-classes';
|
|
17
18
|
import {
|
|
19
|
+
placeholderDefinition,
|
|
18
20
|
selectClass,
|
|
21
|
+
selectClassLabels,
|
|
19
22
|
selectData,
|
|
20
|
-
|
|
23
|
+
selectIsClassFetched,
|
|
21
24
|
selectOrderedClasses,
|
|
22
25
|
slice,
|
|
23
26
|
type StateWithGlobalClasses,
|
|
24
27
|
} from './store';
|
|
25
28
|
import { trackGlobalClasses, type TrackingEvent } from './utils/tracking';
|
|
26
29
|
|
|
27
|
-
const MAX_CLASSES =
|
|
30
|
+
const MAX_CLASSES = 5000;
|
|
28
31
|
|
|
29
32
|
export const GLOBAL_CLASSES_PROVIDER_KEY = 'global-classes';
|
|
30
|
-
const PREGENERATED_LINK_PATTERN = /^global-(preview|frontend)-[a-zA-Z_-]+-css$/;
|
|
33
|
+
const PREGENERATED_LINK_PATTERN = /^global-([0-9]+-)?(preview|frontend)-[a-zA-Z_-]+-css$/;
|
|
31
34
|
|
|
32
35
|
export const globalClassesStylesProvider = createStylesProvider( {
|
|
33
36
|
key: GLOBAL_CLASSES_PROVIDER_KEY,
|
|
@@ -42,21 +45,44 @@ export const globalClassesStylesProvider = createStylesProvider( {
|
|
|
42
45
|
capabilities: getCapabilities(),
|
|
43
46
|
actions: {
|
|
44
47
|
all: () => selectOrderedClasses( getState() ),
|
|
45
|
-
get: ( id ) =>
|
|
48
|
+
get: ( id ) => {
|
|
49
|
+
const state = getState();
|
|
50
|
+
|
|
51
|
+
const isFetched = selectIsClassFetched( state, id );
|
|
52
|
+
const style = selectClass( state, id );
|
|
53
|
+
|
|
54
|
+
// the isFetched flag is based on the existence of the style in the initial data
|
|
55
|
+
// so if the style is created during the same session - it won't be stored as part of the initial data
|
|
56
|
+
if ( isFetched || style ) {
|
|
57
|
+
return style;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
loadExistingClasses( [ id ] );
|
|
61
|
+
|
|
62
|
+
const label = selectClassLabels( state )[ id ] ?? id;
|
|
63
|
+
return placeholderDefinition( id, label );
|
|
64
|
+
},
|
|
46
65
|
resolveCssName: ( id: string ) => {
|
|
47
|
-
|
|
66
|
+
const state = getState();
|
|
67
|
+
const loaded = selectClass( state, id );
|
|
68
|
+
if ( loaded ) {
|
|
69
|
+
return loaded.label;
|
|
70
|
+
}
|
|
71
|
+
const fromIndex = selectClassLabels( state )[ id ];
|
|
72
|
+
return fromIndex ?? id;
|
|
48
73
|
},
|
|
49
74
|
create: ( label, variants: StyleDefinitionVariant[] = [], id?: StyleDefinitionID ) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const existingLabels = Object.values( classes ).map( ( style ) => style.label );
|
|
75
|
+
const existingClasses = Object.entries( selectClassLabels( getState() ) );
|
|
76
|
+
const existingLabels = existingClasses.map( ( [ , classLabel ] ) => classLabel );
|
|
53
77
|
|
|
54
78
|
if ( existingLabels.includes( label ) ) {
|
|
55
79
|
throw new GlobalClassLabelAlreadyExistsError( { context: { label } } );
|
|
56
80
|
}
|
|
57
81
|
|
|
82
|
+
const existingIds = existingClasses.map( ( [ existingId ] ) => existingId );
|
|
83
|
+
|
|
58
84
|
if ( ! id ) {
|
|
59
|
-
id = generateId( 'g-',
|
|
85
|
+
id = generateId( 'g-', existingIds );
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
dispatch(
|
|
@@ -114,10 +140,10 @@ const subscribeWithStates = (
|
|
|
114
140
|
let previousState = selectData( getState() );
|
|
115
141
|
|
|
116
142
|
return subscribeWithSelector(
|
|
117
|
-
( state: StateWithGlobalClasses ) => state
|
|
143
|
+
( state: StateWithGlobalClasses ) => selectData( state ),
|
|
118
144
|
( currentState ) => {
|
|
119
|
-
cb( previousState.items, currentState.
|
|
120
|
-
previousState = currentState
|
|
145
|
+
cb( previousState.items, currentState.items );
|
|
146
|
+
previousState = currentState;
|
|
121
147
|
}
|
|
122
148
|
);
|
|
123
149
|
};
|
package/src/index.ts
CHANGED
|
@@ -5,3 +5,8 @@ export {
|
|
|
5
5
|
export { GLOBAL_CLASSES_URI } from './mcp-integration/classes-resource';
|
|
6
6
|
|
|
7
7
|
export { init } from './init';
|
|
8
|
+
|
|
9
|
+
export { loadExistingClasses } from './load-existing-classes';
|
|
10
|
+
export { addDocumentClasses } from './load-document-classes';
|
|
11
|
+
export type { GlobalClassIndexEntry } from './api';
|
|
12
|
+
export { createLabelsForClasses } from './utils/create-labels-for-classes';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getCurrentDocument } from '@elementor/editor-documents';
|
|
2
|
+
import { type StyleDefinition, type StyleDefinitionID } from '@elementor/editor-styles';
|
|
3
|
+
import { __dispatch as dispatch } from '@elementor/store';
|
|
4
|
+
|
|
5
|
+
import { apiClient, type StyleDefinitionsNullableMap } from './api';
|
|
6
|
+
import { slice } from './store';
|
|
7
|
+
import { createLabelsForClasses } from './utils/create-labels-for-classes';
|
|
8
|
+
|
|
9
|
+
export function styleDefinitionsMapWithoutNull(
|
|
10
|
+
map: StyleDefinitionsNullableMap
|
|
11
|
+
): Record< StyleDefinitionID, StyleDefinition > {
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
Object.entries( map ).filter(
|
|
14
|
+
( entry ): entry is [ StyleDefinitionID, StyleDefinition ] => entry[ 1 ] !== null
|
|
15
|
+
)
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resetGlobalClassesState( globalOrder: StyleDefinitionID[], classLabels: Record< StyleDefinitionID, string > ) {
|
|
20
|
+
dispatch(
|
|
21
|
+
slice.actions.load( {
|
|
22
|
+
preview: { items: {}, order: globalOrder },
|
|
23
|
+
frontend: { items: {}, order: globalOrder },
|
|
24
|
+
classLabels,
|
|
25
|
+
} )
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function loadCurrentDocumentClasses() {
|
|
30
|
+
const previewIndexRes = await apiClient.all( 'preview' );
|
|
31
|
+
const previewIndex = previewIndexRes.data.data;
|
|
32
|
+
const classLabels = createLabelsForClasses( previewIndex );
|
|
33
|
+
const globalOrder = previewIndex.map( ( e ) => e.id );
|
|
34
|
+
|
|
35
|
+
// This is intended to establish the baseline with current labels and order
|
|
36
|
+
// without it we won't be able to properly resolve the styles' class names
|
|
37
|
+
resetGlobalClassesState( globalOrder, classLabels );
|
|
38
|
+
|
|
39
|
+
const postId = getCurrentDocument()?.id;
|
|
40
|
+
if ( ! postId ) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [ previewPostRes, frontendPostRes ] = await Promise.all( [
|
|
45
|
+
apiClient.getStylesForPost( postId, 'preview' ),
|
|
46
|
+
apiClient.getStylesForPost( postId, 'frontend' ),
|
|
47
|
+
] );
|
|
48
|
+
|
|
49
|
+
const previewItems = styleDefinitionsMapWithoutNull( previewPostRes.data.data );
|
|
50
|
+
const frontendItems = styleDefinitionsMapWithoutNull( frontendPostRes.data.data );
|
|
51
|
+
|
|
52
|
+
dispatch(
|
|
53
|
+
slice.actions.load( {
|
|
54
|
+
preview: { items: previewItems, order: globalOrder },
|
|
55
|
+
frontend: { items: frontendItems, order: globalOrder },
|
|
56
|
+
classLabels,
|
|
57
|
+
} )
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function addDocumentClasses( documentId: number ) {
|
|
62
|
+
const [ previewPostRes, frontendPostRes ] = await Promise.all( [
|
|
63
|
+
apiClient.getStylesForPost( documentId, 'preview' ),
|
|
64
|
+
apiClient.getStylesForPost( documentId, 'frontend' ),
|
|
65
|
+
] );
|
|
66
|
+
|
|
67
|
+
const previewItems = styleDefinitionsMapWithoutNull( previewPostRes.data.data );
|
|
68
|
+
const frontendItems = styleDefinitionsMapWithoutNull( frontendPostRes.data.data );
|
|
69
|
+
|
|
70
|
+
dispatch(
|
|
71
|
+
slice.actions.mergeExistingClasses( {
|
|
72
|
+
preview: previewItems,
|
|
73
|
+
frontend: frontendItems,
|
|
74
|
+
} )
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
2
|
+
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
3
|
+
|
|
4
|
+
import { apiClient } from './api';
|
|
5
|
+
import { styleDefinitionsMapWithoutNull } from './load-document-classes';
|
|
6
|
+
import { selectGlobalClasses, slice } from './store';
|
|
7
|
+
|
|
8
|
+
let pendingLoad: Promise< void > | null = null;
|
|
9
|
+
const pendingIds = new Set< StyleDefinitionID >();
|
|
10
|
+
|
|
11
|
+
export async function loadExistingClasses( classIds: StyleDefinitionID[] ): Promise< void > {
|
|
12
|
+
const existingClasses = selectGlobalClasses( getState() );
|
|
13
|
+
const missingIds = classIds.filter( ( id ) => ! ( id in existingClasses ) );
|
|
14
|
+
|
|
15
|
+
if ( missingIds.length === 0 ) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
missingIds.forEach( ( id ) => pendingIds.add( id ) );
|
|
20
|
+
|
|
21
|
+
if ( pendingLoad ) {
|
|
22
|
+
await pendingLoad;
|
|
23
|
+
return loadExistingClasses( classIds );
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pendingLoad = fetchAndMergeClasses();
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await pendingLoad;
|
|
30
|
+
} finally {
|
|
31
|
+
pendingLoad = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function fetchAndMergeClasses(): Promise< void > {
|
|
36
|
+
const idsToFetch = Array.from( pendingIds );
|
|
37
|
+
pendingIds.clear();
|
|
38
|
+
|
|
39
|
+
if ( idsToFetch.length === 0 ) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const previewResponse = await apiClient.getStylesByIds( idsToFetch, 'preview' );
|
|
44
|
+
const frontendResponse = await apiClient.getStylesByIds( idsToFetch, 'frontend' );
|
|
45
|
+
const previewItems = styleDefinitionsMapWithoutNull( previewResponse.data.data );
|
|
46
|
+
const frontendItems = styleDefinitionsMapWithoutNull( frontendResponse.data.data );
|
|
47
|
+
|
|
48
|
+
dispatch( slice.actions.mergeExistingClasses( { preview: previewItems, frontend: frontendItems } ) );
|
|
49
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { type MCPRegistryEntry } from '@elementor/editor-mcp';
|
|
2
|
+
import { __getState as getState } from '@elementor/store';
|
|
2
3
|
|
|
3
4
|
import { globalClassesStylesProvider } from '../global-classes-styles-provider';
|
|
5
|
+
import { selectOrderedClasses } from '../store';
|
|
4
6
|
|
|
5
7
|
export const GLOBAL_CLASSES_URI = 'elementor://global-classes';
|
|
6
8
|
|
|
7
9
|
const STORAGE_KEY = 'elementor-global-classes';
|
|
8
10
|
|
|
9
11
|
const updateLocalStorageCache = () => {
|
|
10
|
-
const classes =
|
|
12
|
+
const classes = selectOrderedClasses( getState() );
|
|
11
13
|
|
|
12
14
|
localStorage.setItem( STORAGE_KEY, JSON.stringify( classes ) );
|
|
13
15
|
};
|
|
@@ -17,10 +17,17 @@ export async function saveGlobalClasses( { context, onApprove }: Options ) {
|
|
|
17
17
|
const state = selectData( getState() );
|
|
18
18
|
const apiAction = context === 'preview' ? apiClient.saveDraft : apiClient.publish;
|
|
19
19
|
const currentContext = context === 'preview' ? selectPreviewInitialData : selectFrontendInitialData;
|
|
20
|
+
const changes = calculateChanges( state, currentContext( getState() ) );
|
|
21
|
+
|
|
22
|
+
const touchedIds = [ ...changes.added, ...changes.modified ];
|
|
23
|
+
const touchedItems = Object.fromEntries(
|
|
24
|
+
touchedIds.map( ( id ) => [ id, state.items[ id ] ] ).filter( ( [ , v ] ) => v )
|
|
25
|
+
);
|
|
26
|
+
|
|
20
27
|
const response = await apiAction( {
|
|
21
|
-
items:
|
|
28
|
+
items: touchedItems,
|
|
22
29
|
order: state.order,
|
|
23
|
-
changes
|
|
30
|
+
changes,
|
|
24
31
|
} );
|
|
25
32
|
|
|
26
33
|
dispatch( slice.actions.reset( { context } ) );
|
|
@@ -29,10 +36,12 @@ export async function saveGlobalClasses( { context, onApprove }: Options ) {
|
|
|
29
36
|
|
|
30
37
|
if ( response?.data?.data?.code === API_ERROR_CODES.DUPLICATED_LABEL ) {
|
|
31
38
|
dispatch( slice.actions.updateMultiple( response.data.data.modifiedLabels ) );
|
|
39
|
+
|
|
32
40
|
trackGlobalClasses( {
|
|
33
41
|
event: 'classPublishConflict',
|
|
34
42
|
numOfConflicts: Object.keys( response.data.data.modifiedLabels ).length,
|
|
35
43
|
} );
|
|
44
|
+
|
|
36
45
|
openDialog( {
|
|
37
46
|
component: (
|
|
38
47
|
<DuplicateLabelDialog
|
|
@@ -48,11 +57,17 @@ function calculateChanges( state: GlobalClasses, initialData: GlobalClasses ) {
|
|
|
48
57
|
const stateIds = Object.keys( state.items );
|
|
49
58
|
const initialDataIds = Object.keys( initialData.items );
|
|
50
59
|
|
|
60
|
+
const { order: stateOrder } = state;
|
|
61
|
+
const { order: initialDataOrder } = initialData;
|
|
62
|
+
|
|
63
|
+
const order = stateOrder.join( ';' ) !== initialDataOrder.join( ';' );
|
|
64
|
+
|
|
51
65
|
return {
|
|
52
66
|
added: stateIds.filter( ( id ) => ! initialDataIds.includes( id ) ),
|
|
53
67
|
deleted: initialDataIds.filter( ( id ) => ! stateIds.includes( id ) ),
|
|
54
68
|
modified: stateIds.filter( ( id ) => {
|
|
55
69
|
return id in initialData.items && hash( state.items[ id ] ) !== hash( initialData.items[ id ] );
|
|
56
70
|
} ),
|
|
71
|
+
order,
|
|
57
72
|
};
|
|
58
73
|
}
|