@elementor/editor-global-classes 4.1.0-manual → 4.2.0-839
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 +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +1008 -700
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1011 -705
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -21
- package/src/api.ts +32 -15
- package/src/components/class-manager/class-manager-button.tsx +16 -6
- package/src/components/class-manager/class-manager-panel.tsx +197 -101
- package/src/components/class-manager/global-classes-list.tsx +62 -14
- package/src/components/class-manager/sortable.tsx +9 -3
- package/src/components/global-styles-import-listener.tsx +10 -37
- package/src/components/populate-store.tsx +10 -24
- package/src/global-classes-styles-provider.ts +48 -15
- package/src/index.ts +9 -0
- package/src/init.ts +11 -5
- 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/mcp-integration/mcp-apply-unapply-global-classes.ts +1 -1
- package/src/mcp-integration/mcp-get-global-class-usages.ts +4 -4
- package/src/save-global-classes.tsx +17 -2
- package/src/store.ts +73 -7
- package/src/sync-with-document.tsx +13 -5
- package/src/utils/create-labels-for-classes.ts +7 -0
- package/src/utils/tracking.ts +14 -6
|
@@ -11,7 +11,12 @@ import {
|
|
|
11
11
|
} from '@elementor/ui';
|
|
12
12
|
|
|
13
13
|
export const SortableProvider = < T extends string >( props: UnstableSortableProviderProps< T > ) => (
|
|
14
|
-
<UnstableSortableProvider
|
|
14
|
+
<UnstableSortableProvider
|
|
15
|
+
restrictAxis
|
|
16
|
+
variant="static"
|
|
17
|
+
dragPlaceholderStyle={ { visibility: 'hidden' } }
|
|
18
|
+
{ ...props }
|
|
19
|
+
/>
|
|
15
20
|
);
|
|
16
21
|
|
|
17
22
|
export type SortableTriggerProps = React.HTMLAttributes< HTMLDivElement >;
|
|
@@ -24,10 +29,11 @@ export const SortableTrigger = ( props: SortableTriggerProps ) => (
|
|
|
24
29
|
|
|
25
30
|
type SortableItemProps = {
|
|
26
31
|
id: UnstableSortableItemProps[ 'id' ];
|
|
32
|
+
style?: React.CSSProperties;
|
|
27
33
|
children: ( props: Partial< UnstableSortableItemRenderProps > ) => React.ReactNode;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
|
-
export const SortableItem = ( { children, id, ...props }: SortableItemProps ) => {
|
|
36
|
+
export const SortableItem = ( { children, id, style, ...props }: SortableItemProps ) => {
|
|
31
37
|
return (
|
|
32
38
|
<UnstableSortableItem
|
|
33
39
|
{ ...props }
|
|
@@ -46,7 +52,7 @@ export const SortableItem = ( { children, id, ...props }: SortableItemProps ) =>
|
|
|
46
52
|
return (
|
|
47
53
|
<Box
|
|
48
54
|
{ ...itemProps }
|
|
49
|
-
style={ itemStyle }
|
|
55
|
+
style={ { ...itemStyle, ...( ! isDragOverlay ? style : null ) } }
|
|
50
56
|
component={ 'li' }
|
|
51
57
|
role="listitem"
|
|
52
58
|
sx={ {
|
|
@@ -1,59 +1,32 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
+
import { GLOBAL_STYLES_IMPORTED_EVENT, type ImportedGlobalStylesPayload } from '@elementor/editor-canvas';
|
|
2
3
|
import { __useDispatch as useDispatch } from '@elementor/store';
|
|
3
4
|
|
|
4
|
-
import { apiClient } from '../api';
|
|
5
5
|
import { slice } from '../store';
|
|
6
6
|
|
|
7
7
|
export function GlobalStylesImportListener() {
|
|
8
8
|
const dispatch = useDispatch();
|
|
9
9
|
|
|
10
10
|
useEffect( () => {
|
|
11
|
-
const handleGlobalStylesImported = ( event: CustomEvent ) => {
|
|
12
|
-
const importedClasses = event.detail?.global_classes;
|
|
11
|
+
const handleGlobalStylesImported = ( event: CustomEvent< ImportedGlobalStylesPayload > ) => {
|
|
12
|
+
const importedClasses = event.detail?.global_classes as ImportedGlobalStylesPayload[ 'global_classes' ];
|
|
13
13
|
|
|
14
14
|
if ( importedClasses?.items && importedClasses?.order ) {
|
|
15
|
+
const { items } = importedClasses;
|
|
16
|
+
|
|
15
17
|
dispatch(
|
|
16
|
-
slice.actions.
|
|
17
|
-
preview:
|
|
18
|
-
|
|
19
|
-
order: importedClasses.order,
|
|
20
|
-
},
|
|
21
|
-
frontend: {
|
|
22
|
-
items: importedClasses.items,
|
|
23
|
-
order: importedClasses.order,
|
|
24
|
-
},
|
|
18
|
+
slice.actions.mergeExistingClasses( {
|
|
19
|
+
preview: items,
|
|
20
|
+
frontend: items,
|
|
25
21
|
} )
|
|
26
22
|
);
|
|
27
23
|
}
|
|
28
|
-
|
|
29
|
-
Promise.all( [ apiClient.all( 'preview' ), apiClient.all( 'frontend' ) ] )
|
|
30
|
-
.then( ( [ previewRes, frontendRes ] ) => {
|
|
31
|
-
const { data: previewData } = previewRes;
|
|
32
|
-
const { data: frontendData } = frontendRes;
|
|
33
|
-
|
|
34
|
-
dispatch(
|
|
35
|
-
slice.actions.load( {
|
|
36
|
-
preview: {
|
|
37
|
-
items: previewData.data,
|
|
38
|
-
order: previewData.meta.order,
|
|
39
|
-
},
|
|
40
|
-
frontend: {
|
|
41
|
-
items: frontendData.data,
|
|
42
|
-
order: frontendData.meta.order,
|
|
43
|
-
},
|
|
44
|
-
} )
|
|
45
|
-
);
|
|
46
|
-
} )
|
|
47
|
-
.catch( () => {} );
|
|
48
24
|
};
|
|
49
25
|
|
|
50
|
-
window.addEventListener(
|
|
26
|
+
window.addEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported as EventListener );
|
|
51
27
|
|
|
52
28
|
return () => {
|
|
53
|
-
window.removeEventListener(
|
|
54
|
-
'elementor/global-styles/imported',
|
|
55
|
-
handleGlobalStylesImported as EventListener
|
|
56
|
-
);
|
|
29
|
+
window.removeEventListener( GLOBAL_STYLES_IMPORTED_EVENT, handleGlobalStylesImported as EventListener );
|
|
57
30
|
};
|
|
58
31
|
}, [ dispatch ] );
|
|
59
32
|
|
|
@@ -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
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
generateId,
|
|
3
|
+
type StyleDefinition,
|
|
4
|
+
type StyleDefinitionID,
|
|
5
|
+
type StyleDefinitionVariant,
|
|
6
|
+
} from '@elementor/editor-styles';
|
|
2
7
|
import { createStylesProvider } from '@elementor/editor-styles-repository';
|
|
3
8
|
import {
|
|
4
9
|
__dispatch as dispatch,
|
|
@@ -9,20 +14,23 @@ import { __ } from '@wordpress/i18n';
|
|
|
9
14
|
|
|
10
15
|
import { getCapabilities } from './capabilities';
|
|
11
16
|
import { GlobalClassLabelAlreadyExistsError, GlobalClassTrackingError } from './errors';
|
|
17
|
+
import { loadExistingClasses } from './load-existing-classes';
|
|
12
18
|
import {
|
|
19
|
+
placeholderDefinition,
|
|
13
20
|
selectClass,
|
|
21
|
+
selectClassLabels,
|
|
14
22
|
selectData,
|
|
15
|
-
|
|
23
|
+
selectIsClassFetched,
|
|
16
24
|
selectOrderedClasses,
|
|
17
25
|
slice,
|
|
18
26
|
type StateWithGlobalClasses,
|
|
19
27
|
} from './store';
|
|
20
28
|
import { trackGlobalClasses, type TrackingEvent } from './utils/tracking';
|
|
21
29
|
|
|
22
|
-
const MAX_CLASSES =
|
|
30
|
+
const MAX_CLASSES = 5000;
|
|
23
31
|
|
|
24
32
|
export const GLOBAL_CLASSES_PROVIDER_KEY = 'global-classes';
|
|
25
|
-
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$/;
|
|
26
34
|
|
|
27
35
|
export const globalClassesStylesProvider = createStylesProvider( {
|
|
28
36
|
key: GLOBAL_CLASSES_PROVIDER_KEY,
|
|
@@ -37,21 +45,45 @@ export const globalClassesStylesProvider = createStylesProvider( {
|
|
|
37
45
|
capabilities: getCapabilities(),
|
|
38
46
|
actions: {
|
|
39
47
|
all: () => selectOrderedClasses( getState() ),
|
|
40
|
-
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
|
+
},
|
|
41
65
|
resolveCssName: ( id: string ) => {
|
|
42
|
-
|
|
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;
|
|
43
73
|
},
|
|
44
|
-
create: ( label, variants: StyleDefinitionVariant[] = [] ) => {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const existingLabels = Object.values( classes ).map( ( style ) => style.label );
|
|
74
|
+
create: ( label, variants: StyleDefinitionVariant[] = [], id?: StyleDefinitionID ) => {
|
|
75
|
+
const existingClasses = Object.entries( selectClassLabels( getState() ) );
|
|
76
|
+
const existingLabels = existingClasses.map( ( [ , classLabel ] ) => classLabel );
|
|
48
77
|
|
|
49
78
|
if ( existingLabels.includes( label ) ) {
|
|
50
79
|
throw new GlobalClassLabelAlreadyExistsError( { context: { label } } );
|
|
51
80
|
}
|
|
52
81
|
|
|
53
|
-
const existingIds =
|
|
54
|
-
|
|
82
|
+
const existingIds = existingClasses.map( ( [ existingId ] ) => existingId );
|
|
83
|
+
|
|
84
|
+
if ( ! id ) {
|
|
85
|
+
id = generateId( 'g-', existingIds );
|
|
86
|
+
}
|
|
55
87
|
|
|
56
88
|
dispatch(
|
|
57
89
|
slice.actions.add( {
|
|
@@ -80,6 +112,7 @@ export const globalClassesStylesProvider = createStylesProvider( {
|
|
|
80
112
|
id: args.id,
|
|
81
113
|
meta: args.meta,
|
|
82
114
|
props: args.props,
|
|
115
|
+
mode: args.mode,
|
|
83
116
|
} )
|
|
84
117
|
);
|
|
85
118
|
},
|
|
@@ -107,10 +140,10 @@ const subscribeWithStates = (
|
|
|
107
140
|
let previousState = selectData( getState() );
|
|
108
141
|
|
|
109
142
|
return subscribeWithSelector(
|
|
110
|
-
( state: StateWithGlobalClasses ) => state
|
|
143
|
+
( state: StateWithGlobalClasses ) => selectData( state ),
|
|
111
144
|
( currentState ) => {
|
|
112
|
-
cb( previousState.items, currentState.
|
|
113
|
-
previousState = currentState
|
|
145
|
+
cb( previousState.items, currentState.items );
|
|
146
|
+
previousState = currentState;
|
|
114
147
|
}
|
|
115
148
|
);
|
|
116
149
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ClassManagerPanelEmbedded,
|
|
3
|
+
type ClassManagerPanelEmbeddedProps,
|
|
4
|
+
} from './components/class-manager/class-manager-panel';
|
|
1
5
|
export { GLOBAL_CLASSES_URI } from './mcp-integration/classes-resource';
|
|
2
6
|
|
|
3
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';
|
package/src/init.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { getMCPByDomain } from '@elementor/editor-mcp';
|
|
8
8
|
import { __registerPanel as registerPanel } from '@elementor/editor-panels';
|
|
9
9
|
import { stylesRepository } from '@elementor/editor-styles-repository';
|
|
10
|
+
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
10
11
|
import { __registerSlice as registerSlice } from '@elementor/store';
|
|
11
12
|
|
|
12
13
|
import { ClassManagerButton } from './components/class-manager/class-manager-button';
|
|
@@ -23,7 +24,10 @@ import { SyncWithDocumentSave } from './sync-with-document';
|
|
|
23
24
|
|
|
24
25
|
export function init() {
|
|
25
26
|
registerSlice( slice );
|
|
26
|
-
|
|
27
|
+
|
|
28
|
+
if ( ! isExperimentActive( 'e_editor_design_system_panel' ) ) {
|
|
29
|
+
registerPanel( panel );
|
|
30
|
+
}
|
|
27
31
|
|
|
28
32
|
stylesRepository.register( globalClassesStylesProvider );
|
|
29
33
|
|
|
@@ -47,10 +51,12 @@ export function init() {
|
|
|
47
51
|
component: PrefetchCssClassUsage,
|
|
48
52
|
} );
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
if ( ! isExperimentActive( 'e_editor_design_system_panel' ) ) {
|
|
55
|
+
injectIntoLogic( {
|
|
56
|
+
id: 'global-classes-open-panel-from-url',
|
|
57
|
+
component: OpenPanelFromUrl,
|
|
58
|
+
} );
|
|
59
|
+
}
|
|
54
60
|
|
|
55
61
|
injectIntoCssClassConvert( {
|
|
56
62
|
id: 'global-classes-convert-from-local-class',
|
|
@@ -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
|
};
|
|
@@ -79,7 +79,7 @@ export default function initMcpApplyUnapplyGlobalClasses( server: MCPRegistryEnt
|
|
|
79
79
|
- Make sure you have the correct class ID that you want to unapply.
|
|
80
80
|
|
|
81
81
|
<note>
|
|
82
|
-
If the user want to unapply a class by it's name and not ID,
|
|
82
|
+
If the user want to unapply a class by it's name and not ID, retrieve the id from the list, available at uri elementor://global-classes
|
|
83
83
|
</note>
|
|
84
84
|
`,
|
|
85
85
|
handler: async ( params ) => {
|
|
@@ -12,7 +12,7 @@ export default function initMcpApplyGetGlobalClassUsages( reg: MCPRegistryEntry
|
|
|
12
12
|
classId: z
|
|
13
13
|
.string()
|
|
14
14
|
.describe(
|
|
15
|
-
'The ID of the class, not visible to the user. To
|
|
15
|
+
'The ID of the class, not visible to the user. To retrieve the name of the class, use the "list-global-classes" tool'
|
|
16
16
|
),
|
|
17
17
|
usages: z.array(
|
|
18
18
|
z.object( {
|
|
@@ -32,13 +32,13 @@ export default function initMcpApplyGetGlobalClassUsages( reg: MCPRegistryEntry
|
|
|
32
32
|
intelligencePriority: 0.6,
|
|
33
33
|
speedPriority: 0.8,
|
|
34
34
|
},
|
|
35
|
-
description: `
|
|
35
|
+
description: `Retrieve the usages of global-classes ACROSS PAGES designed by Elementor editor.
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## Prerequisites: CRITICAL
|
|
38
38
|
- The list of global classes and their applid values is available at resource uri elementor://global-classes
|
|
39
39
|
|
|
40
40
|
## When to use this tool:
|
|
41
|
-
- When a user requests to see where a specific global class is being used
|
|
41
|
+
- When a user requests to see where a specific global class is being used across the site.
|
|
42
42
|
- When you need to manage or clean up unused global classes.
|
|
43
43
|
- Before deleting a global class, to ensure it is not in use in any other pages.
|
|
44
44
|
|
|
@@ -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
|
}
|