@elementor/editor-components 3.33.0-201 → 3.33.0-202

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.
@@ -1,3 +1,4 @@
1
+ import { type V1ElementData } from '@elementor/editor-elements';
1
2
  import {
2
3
  __createSelector as createSelector,
3
4
  __createSlice as createSlice,
@@ -10,10 +11,15 @@ import { createComponent, loadComponents } from './thunks';
10
11
 
11
12
  type GetComponentResponse = Component[];
12
13
 
14
+ export type UnpublishedComponent = Component & {
15
+ content: V1ElementData[];
16
+ };
17
+
13
18
  type Status = 'idle' | 'pending' | 'error';
14
19
 
15
20
  type ComponentsState = {
16
21
  data: Component[];
22
+ unpublishedData: UnpublishedComponent[];
17
23
  loadStatus: Status;
18
24
  createStatus: Status;
19
25
  styles: StylesDefinition;
@@ -23,6 +29,7 @@ type ComponentsSlice = SliceState< typeof slice >;
23
29
 
24
30
  export const initialState: ComponentsState = {
25
31
  data: [],
32
+ unpublishedData: [],
26
33
  loadStatus: 'idle',
27
34
  createStatus: 'idle',
28
35
  styles: {},
@@ -33,12 +40,22 @@ export const slice = createSlice( {
33
40
  name: SLICE_NAME,
34
41
  initialState,
35
42
  reducers: {
36
- add: ( state, { payload } ) => {
37
- state.data = { ...payload };
43
+ add: ( state, { payload }: PayloadAction< Component | Component[] > ) => {
44
+ if ( Array.isArray( payload ) ) {
45
+ state.data = [ ...state.data, ...payload ];
46
+ } else {
47
+ state.data.unshift( payload );
48
+ }
38
49
  },
39
- load: ( state, { payload } ) => {
50
+ load: ( state, { payload }: PayloadAction< Component[] > ) => {
40
51
  state.data = payload;
41
52
  },
53
+ addUnpublished: ( state, { payload } ) => {
54
+ state.unpublishedData.unshift( payload );
55
+ },
56
+ resetUnpublished: ( state ) => {
57
+ state.unpublishedData = [];
58
+ },
42
59
  removeStyles( state, { payload }: PayloadAction< { id: ComponentId } > ) {
43
60
  const { [ payload.id ]: _, ...rest } = state.styles;
44
61
 
@@ -79,8 +96,20 @@ const selectData = ( state: ComponentsSlice ) => state[ SLICE_NAME ].data;
79
96
  const selectLoadStatus = ( state: ComponentsSlice ) => state[ SLICE_NAME ].loadStatus;
80
97
  const selectCreateStatus = ( state: ComponentsSlice ) => state[ SLICE_NAME ].createStatus;
81
98
  const selectStylesDefinitions = ( state: ComponentsSlice ) => state[ SLICE_NAME ].styles ?? {};
99
+ const selectUnpublishedData = ( state: ComponentsSlice ) => state[ SLICE_NAME ].unpublishedData;
82
100
 
83
- export const selectComponents = createSelector( selectData, ( data: Component[] ) => data );
101
+ export const selectComponents = createSelector(
102
+ selectData,
103
+ selectUnpublishedData,
104
+ ( data: Component[], unpublishedData: UnpublishedComponent[] ) => [
105
+ ...unpublishedData.map( ( item ) => ( { id: item.id, name: item.name } ) ),
106
+ ...data,
107
+ ]
108
+ );
109
+ export const selectUnpublishedComponents = createSelector(
110
+ selectUnpublishedData,
111
+ ( unpublishedData: UnpublishedComponent[] ) => unpublishedData
112
+ );
84
113
  export const selectLoadIsPending = createSelector( selectLoadStatus, ( status ) => status === 'pending' );
85
114
  export const selectLoadIsError = createSelector( selectLoadStatus, ( status ) => status === 'error' );
86
115
  export const selectCreateIsPending = createSelector( selectCreateStatus, ( status ) => status === 'pending' );
package/src/types.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { type V1ElementModelProps, type V1ElementSettingsProps } from '@elementor/editor-elements';
2
- import { type NumberPropValue } from '@elementor/editor-props';
3
2
  import type { StyleDefinition } from '@elementor/editor-styles';
4
3
 
5
4
  export type ComponentFormValues = {
@@ -15,9 +14,18 @@ export type Component = {
15
14
  name: string;
16
15
  };
17
16
 
17
+ export type DocumentStatus = 'publish' | 'draft' | 'autosave';
18
+
18
19
  export type Element = V1ElementModelProps & {
19
20
  elements?: Element[];
20
21
  settings?: V1ElementSettingsProps & {
21
- component_id?: NumberPropValue;
22
+ component?: {
23
+ $$type: 'component-id';
24
+ value: number;
25
+ };
22
26
  };
23
27
  };
28
+
29
+ export type ExtendedWindow = Window & {
30
+ elementorCommon: Record< string, unknown >;
31
+ };
@@ -0,0 +1,99 @@
1
+ import { updateElementSettings, type V1ElementData } from '@elementor/editor-elements';
2
+ import { type TransformablePropValue } from '@elementor/editor-props';
3
+ import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
4
+
5
+ import { apiClient } from '../api';
6
+ import { selectUnpublishedComponents, slice, type UnpublishedComponent } from '../store/store';
7
+ import { type DocumentStatus } from '../types';
8
+
9
+ type Container = {
10
+ model: {
11
+ get: ( key: 'elements' ) => {
12
+ toJSON: () => V1ElementData[];
13
+ };
14
+ };
15
+ };
16
+
17
+ export const beforeSave = async ( { container, status }: { container: Container; status: DocumentStatus } ) => {
18
+ const unpublishedComponents = selectUnpublishedComponents( getState() );
19
+
20
+ if ( ! unpublishedComponents.length ) {
21
+ return;
22
+ }
23
+
24
+ try {
25
+ const tempIdToComponentId = await createComponents( unpublishedComponents, status );
26
+
27
+ const elements = container.model.get( 'elements' ).toJSON();
28
+ updateComponentInstances( elements, tempIdToComponentId );
29
+
30
+ dispatch(
31
+ slice.actions.add(
32
+ unpublishedComponents.map( ( component ) => ( {
33
+ id: tempIdToComponentId.get( component.id ) as number,
34
+ name: component.name,
35
+ } ) )
36
+ )
37
+ );
38
+ dispatch( slice.actions.resetUnpublished() );
39
+ } catch ( error ) {
40
+ throw new Error( `Failed to publish components and update component instances: ${ error }` );
41
+ }
42
+ };
43
+
44
+ async function createComponents(
45
+ components: UnpublishedComponent[],
46
+ status: DocumentStatus
47
+ ): Promise< Map< number, number > > {
48
+ const tempIdToComponentId = new Map< number, number >();
49
+
50
+ const promises = components.map( ( component ) => {
51
+ return apiClient.create( { name: component.name, content: component.content, status } ).then( ( response ) => {
52
+ tempIdToComponentId.set( component.id, response.component_id );
53
+ } );
54
+ } );
55
+
56
+ await Promise.all( promises );
57
+
58
+ return tempIdToComponentId;
59
+ }
60
+
61
+ function updateComponentInstances( elements: V1ElementData[], tempIdToComponentId: Map< number, number > ): void {
62
+ elements.forEach( ( element ) => {
63
+ const { shouldUpdate, newComponentId } = shouldUpdateElement( element, tempIdToComponentId );
64
+ if ( shouldUpdate ) {
65
+ updateElementComponentId( element.id, newComponentId );
66
+ }
67
+
68
+ if ( element.elements ) {
69
+ updateComponentInstances( element.elements, tempIdToComponentId );
70
+ }
71
+ } );
72
+ }
73
+
74
+ function shouldUpdateElement(
75
+ element: V1ElementData,
76
+ tempIdToComponentId: Map< number, number >
77
+ ): { shouldUpdate: true; newComponentId: number } | { shouldUpdate: false; newComponentId: null } {
78
+ if ( element.widgetType === 'e-component' ) {
79
+ const currentComponentId = ( element.settings?.component as TransformablePropValue< 'component-id', number > )
80
+ ?.value;
81
+ if ( currentComponentId && tempIdToComponentId.has( currentComponentId ) ) {
82
+ return { shouldUpdate: true, newComponentId: tempIdToComponentId.get( currentComponentId ) as number };
83
+ }
84
+ }
85
+ return { shouldUpdate: false, newComponentId: null };
86
+ }
87
+
88
+ function updateElementComponentId( elementId: string, componentId: number ): void {
89
+ updateElementSettings( {
90
+ id: elementId,
91
+ props: {
92
+ component: {
93
+ $$type: 'component-id',
94
+ value: componentId,
95
+ },
96
+ },
97
+ withHistory: false,
98
+ } );
99
+ }