@elementor/editor-components 3.35.0-410 → 3.35.0-412
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 +387 -170
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +350 -132
- package/dist/index.mjs.map +1 -1
- package/package.json +22 -22
- package/src/api.ts +15 -1
- package/src/components/component-panel-header/component-panel-header.tsx +11 -2
- package/src/components/components-tab/components-item.tsx +119 -37
- package/src/components/components-tab/components-list.tsx +8 -1
- package/src/components/create-component-form/utils/component-form-schema.ts +9 -11
- package/src/components/create-component-form/utils/replace-element-with-component.ts +0 -1
- package/src/components/edit-component/edit-component.tsx +31 -1
- package/src/create-component-type.ts +85 -1
- package/src/store/actions/rename-component.ts +7 -0
- package/src/store/store.ts +29 -0
- package/src/sync/before-save.ts +2 -0
- package/src/sync/update-component-title-before-save.ts +18 -0
- package/src/types.ts +5 -0
- package/src/utils/component-name-validation.ts +27 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-components",
|
|
3
3
|
"description": "Elementor editor components",
|
|
4
|
-
"version": "3.35.0-
|
|
4
|
+
"version": "3.35.0-412",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -40,30 +40,30 @@
|
|
|
40
40
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@elementor/editor": "3.35.0-
|
|
44
|
-
"@elementor/editor-canvas": "3.35.0-
|
|
45
|
-
"@elementor/editor-controls": "3.35.0-
|
|
46
|
-
"@elementor/editor-documents": "3.35.0-
|
|
47
|
-
"@elementor/editor-editing-panel": "3.35.0-
|
|
48
|
-
"@elementor/editor-elements": "3.35.0-
|
|
49
|
-
"@elementor/editor-elements-panel": "3.35.0-
|
|
50
|
-
"@elementor/editor-mcp": "3.35.0-
|
|
51
|
-
"@elementor/editor-panels": "3.35.0-
|
|
52
|
-
"@elementor/editor-props": "3.35.0-
|
|
53
|
-
"@elementor/editor-styles-repository": "3.35.0-
|
|
54
|
-
"@elementor/editor-ui": "3.35.0-
|
|
55
|
-
"@elementor/editor-v1-adapters": "3.35.0-
|
|
56
|
-
"@elementor/http-client": "3.35.0-
|
|
43
|
+
"@elementor/editor": "3.35.0-412",
|
|
44
|
+
"@elementor/editor-canvas": "3.35.0-412",
|
|
45
|
+
"@elementor/editor-controls": "3.35.0-412",
|
|
46
|
+
"@elementor/editor-documents": "3.35.0-412",
|
|
47
|
+
"@elementor/editor-editing-panel": "3.35.0-412",
|
|
48
|
+
"@elementor/editor-elements": "3.35.0-412",
|
|
49
|
+
"@elementor/editor-elements-panel": "3.35.0-412",
|
|
50
|
+
"@elementor/editor-mcp": "3.35.0-412",
|
|
51
|
+
"@elementor/editor-panels": "3.35.0-412",
|
|
52
|
+
"@elementor/editor-props": "3.35.0-412",
|
|
53
|
+
"@elementor/editor-styles-repository": "3.35.0-412",
|
|
54
|
+
"@elementor/editor-ui": "3.35.0-412",
|
|
55
|
+
"@elementor/editor-v1-adapters": "3.35.0-412",
|
|
56
|
+
"@elementor/http-client": "3.35.0-412",
|
|
57
57
|
"@elementor/icons": "^1.63.0",
|
|
58
|
-
"@elementor/mixpanel": "3.35.0-
|
|
59
|
-
"@elementor/query": "3.35.0-
|
|
60
|
-
"@elementor/schema": "3.35.0-
|
|
61
|
-
"@elementor/store": "3.35.0-
|
|
58
|
+
"@elementor/mixpanel": "3.35.0-412",
|
|
59
|
+
"@elementor/query": "3.35.0-412",
|
|
60
|
+
"@elementor/schema": "3.35.0-412",
|
|
61
|
+
"@elementor/store": "3.35.0-412",
|
|
62
62
|
"@elementor/ui": "1.36.17",
|
|
63
|
-
"@elementor/utils": "3.35.0-
|
|
63
|
+
"@elementor/utils": "3.35.0-412",
|
|
64
64
|
"@wordpress/i18n": "^5.13.0",
|
|
65
|
-
"@elementor/editor-notifications": "3.35.0-
|
|
66
|
-
"@elementor/editor-current-user": "3.35.0-
|
|
65
|
+
"@elementor/editor-notifications": "3.35.0-412",
|
|
66
|
+
"@elementor/editor-current-user": "3.35.0-412"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"react": "^18.3.1",
|
package/src/api.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { type V1ElementData } from '@elementor/editor-elements';
|
|
|
2
2
|
import { ajax } from '@elementor/editor-v1-adapters';
|
|
3
3
|
import { type HttpResponse, httpService } from '@elementor/http-client';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type DocumentSaveStatus,
|
|
7
|
+
type OverridableProps,
|
|
8
|
+
type PublishedComponent,
|
|
9
|
+
type UpdatedComponentName,
|
|
10
|
+
} from './types';
|
|
6
11
|
|
|
7
12
|
const BASE_URL = 'elementor/v1/components';
|
|
8
13
|
|
|
@@ -104,6 +109,15 @@ export const apiClient = {
|
|
|
104
109
|
}
|
|
105
110
|
)
|
|
106
111
|
.then( ( res ) => res.data.data ),
|
|
112
|
+
updateComponentTitle: ( updatedComponentNames: UpdatedComponentName[] ) =>
|
|
113
|
+
httpService()
|
|
114
|
+
.post< { data: { failedIds: number[]; successIds: number[]; success: boolean } } >(
|
|
115
|
+
`${ BASE_URL }/update-titles`,
|
|
116
|
+
{
|
|
117
|
+
components: updatedComponentNames,
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
.then( ( res ) => res.data.data ),
|
|
107
121
|
validate: async ( payload: ValidateComponentsPayload ) =>
|
|
108
122
|
await httpService()
|
|
109
123
|
.post< HttpResponse< ValidateComponentsResponse > >( `${ BASE_URL }/create-validate`, payload )
|
|
@@ -2,11 +2,12 @@ import * as React from 'react';
|
|
|
2
2
|
import { useSuppressedMessage } from '@elementor/editor-current-user';
|
|
3
3
|
import { getV1DocumentsManager } from '@elementor/editor-documents';
|
|
4
4
|
import { ArrowLeftIcon, ComponentsFilledIcon } from '@elementor/icons';
|
|
5
|
+
import { __getState as getState } from '@elementor/store';
|
|
5
6
|
import { Box, Divider, IconButton, Stack, Tooltip, Typography } from '@elementor/ui';
|
|
6
7
|
import { __ } from '@wordpress/i18n';
|
|
7
8
|
|
|
8
9
|
import { useNavigateBack } from '../../hooks/use-navigate-back';
|
|
9
|
-
import { useCurrentComponentId } from '../../store/store';
|
|
10
|
+
import { type ComponentsSlice, SLICE_NAME, useCurrentComponentId } from '../../store/store';
|
|
10
11
|
import { usePanelActions } from '../component-properties-panel/component-properties-panel';
|
|
11
12
|
import { ComponentIntroduction } from '../components-tab/component-introduction';
|
|
12
13
|
import { ComponentsBadge } from './component-badge';
|
|
@@ -69,7 +70,15 @@ export const ComponentPanelHeader = () => {
|
|
|
69
70
|
);
|
|
70
71
|
};
|
|
71
72
|
|
|
72
|
-
function getComponentName() {
|
|
73
|
+
function getComponentName(): string {
|
|
74
|
+
const state = getState() as ComponentsSlice;
|
|
75
|
+
const path = state[ SLICE_NAME ].path;
|
|
76
|
+
const { instanceTitle } = path.at( -1 ) ?? {};
|
|
77
|
+
|
|
78
|
+
if ( instanceTitle ) {
|
|
79
|
+
return instanceTitle;
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
const documentsManager = getV1DocumentsManager();
|
|
74
83
|
const currentDocument = documentsManager.getCurrent();
|
|
75
84
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { useRef } from 'react';
|
|
2
3
|
import { endDragElementFromPanel, startDragElementFromPanel } from '@elementor/editor-canvas';
|
|
3
4
|
import { dropElement, type DropElementParams, type V1ElementData } from '@elementor/editor-elements';
|
|
4
|
-
import { EllipsisWithTooltip, MenuListItem } from '@elementor/editor-ui';
|
|
5
|
+
import { EditableField, EllipsisWithTooltip, MenuListItem, useEditable, WarningInfotip } from '@elementor/editor-ui';
|
|
5
6
|
import { ComponentsIcon, DotsVerticalIcon } from '@elementor/icons';
|
|
6
7
|
import {
|
|
7
8
|
bindMenu,
|
|
@@ -12,6 +13,8 @@ import {
|
|
|
12
13
|
ListItemIcon,
|
|
13
14
|
Menu,
|
|
14
15
|
Stack,
|
|
16
|
+
styled,
|
|
17
|
+
type Theme,
|
|
15
18
|
Typography,
|
|
16
19
|
usePopupState,
|
|
17
20
|
} from '@elementor/ui';
|
|
@@ -20,14 +23,29 @@ import { __ } from '@wordpress/i18n';
|
|
|
20
23
|
import { archiveComponent } from '../../store/actions/archive-component';
|
|
21
24
|
import { loadComponentsAssets } from '../../store/actions/load-components-assets';
|
|
22
25
|
import { type Component } from '../../types';
|
|
26
|
+
import { validateComponentName } from '../../utils/component-name-validation';
|
|
23
27
|
import { getContainerForNewElement } from '../../utils/get-container-for-new-element';
|
|
24
28
|
import { createComponentModel } from '../create-component-form/utils/replace-element-with-component';
|
|
25
29
|
|
|
26
30
|
type ComponentItemProps = {
|
|
27
31
|
component: Omit< Component, 'id' > & { id?: number };
|
|
32
|
+
renameComponent: ( newName: string ) => void;
|
|
28
33
|
};
|
|
29
34
|
|
|
30
|
-
export const ComponentItem = ( { component }: ComponentItemProps ) => {
|
|
35
|
+
export const ComponentItem = ( { component, renameComponent }: ComponentItemProps ) => {
|
|
36
|
+
const itemRef = useRef< HTMLElement >( null );
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
ref: editableRef,
|
|
40
|
+
isEditing,
|
|
41
|
+
openEditMode,
|
|
42
|
+
error,
|
|
43
|
+
getProps: getEditableProps,
|
|
44
|
+
} = useEditable( {
|
|
45
|
+
value: component.name,
|
|
46
|
+
onSubmit: renameComponent,
|
|
47
|
+
validation: validateComponentTitle,
|
|
48
|
+
} );
|
|
31
49
|
const componentModel = createComponentModel( component );
|
|
32
50
|
|
|
33
51
|
const popupState = usePopupState( {
|
|
@@ -57,48 +75,66 @@ export const ComponentItem = ( { component }: ComponentItemProps ) => {
|
|
|
57
75
|
|
|
58
76
|
return (
|
|
59
77
|
<Stack>
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
border: 'solid 1px',
|
|
67
|
-
borderColor: 'divider',
|
|
68
|
-
py: 0.5,
|
|
69
|
-
px: 1,
|
|
70
|
-
display: 'flex',
|
|
71
|
-
width: '100%',
|
|
72
|
-
alignItems: 'center',
|
|
73
|
-
gap: 1,
|
|
74
|
-
} }
|
|
78
|
+
<WarningInfotip
|
|
79
|
+
open={ Boolean( error ) }
|
|
80
|
+
text={ error ?? '' }
|
|
81
|
+
placement="bottom"
|
|
82
|
+
width={ itemRef.current?.getBoundingClientRect().width }
|
|
83
|
+
offset={ [ 0, -15 ] }
|
|
75
84
|
>
|
|
76
|
-
<
|
|
77
|
-
|
|
85
|
+
<ListItemButton
|
|
86
|
+
draggable
|
|
87
|
+
onDragStart={ ( event: React.DragEvent ) => startDragElementFromPanel( componentModel, event ) }
|
|
88
|
+
onDragEnd={ handleDragEnd }
|
|
89
|
+
shape="rounded"
|
|
90
|
+
ref={ itemRef }
|
|
78
91
|
sx={ {
|
|
92
|
+
border: 'solid 1px',
|
|
93
|
+
borderColor: 'divider',
|
|
94
|
+
py: 0.5,
|
|
95
|
+
px: 1,
|
|
79
96
|
display: 'flex',
|
|
97
|
+
width: '100%',
|
|
80
98
|
alignItems: 'center',
|
|
81
99
|
gap: 1,
|
|
82
|
-
minWidth: 0,
|
|
83
|
-
flexGrow: 1,
|
|
84
100
|
} }
|
|
85
101
|
>
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
<Box
|
|
103
|
+
display="flex"
|
|
104
|
+
alignItems="center"
|
|
105
|
+
gap={ 1 }
|
|
106
|
+
minWidth={ 0 }
|
|
107
|
+
flexGrow={ 1 }
|
|
108
|
+
onClick={ handleClick }
|
|
109
|
+
>
|
|
110
|
+
<ListItemIcon size="tiny">
|
|
111
|
+
<ComponentsIcon fontSize="tiny" />
|
|
112
|
+
</ListItemIcon>
|
|
113
|
+
<Indicator isActive={ isEditing } isError={ !! error }>
|
|
114
|
+
<Box display="flex" flex={ 1 } minWidth={ 0 } flexGrow={ 1 }>
|
|
115
|
+
{ isEditing ? (
|
|
116
|
+
<EditableField
|
|
117
|
+
ref={ editableRef }
|
|
118
|
+
as={ Typography }
|
|
119
|
+
variant="caption"
|
|
120
|
+
{ ...getEditableProps() }
|
|
121
|
+
/>
|
|
122
|
+
) : (
|
|
123
|
+
<EllipsisWithTooltip
|
|
124
|
+
title={ component.name }
|
|
125
|
+
as={ Typography }
|
|
126
|
+
variant="caption"
|
|
127
|
+
color="text.primary"
|
|
128
|
+
/>
|
|
129
|
+
) }
|
|
130
|
+
</Box>
|
|
131
|
+
</Indicator>
|
|
96
132
|
</Box>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</
|
|
101
|
-
</
|
|
133
|
+
<IconButton size="tiny" { ...bindTrigger( popupState ) } aria-label="More actions">
|
|
134
|
+
<DotsVerticalIcon fontSize="tiny" />
|
|
135
|
+
</IconButton>
|
|
136
|
+
</ListItemButton>
|
|
137
|
+
</WarningInfotip>
|
|
102
138
|
<Menu
|
|
103
139
|
{ ...bindMenu( popupState ) }
|
|
104
140
|
anchorOrigin={ {
|
|
@@ -110,8 +146,19 @@ export const ComponentItem = ( { component }: ComponentItemProps ) => {
|
|
|
110
146
|
horizontal: 'right',
|
|
111
147
|
} }
|
|
112
148
|
>
|
|
149
|
+
<MenuListItem
|
|
150
|
+
sx={ { minWidth: '160px' } }
|
|
151
|
+
onClick={ () => {
|
|
152
|
+
popupState.close();
|
|
153
|
+
openEditMode();
|
|
154
|
+
} }
|
|
155
|
+
>
|
|
156
|
+
{ __( 'Rename', 'elementor' ) }
|
|
157
|
+
</MenuListItem>
|
|
113
158
|
<MenuListItem sx={ { minWidth: '160px' } } onClick={ handleArchiveClick }>
|
|
114
|
-
{
|
|
159
|
+
<Typography variant="caption" sx={ { color: 'error.light' } }>
|
|
160
|
+
{ __( 'Archive', 'elementor' ) }
|
|
161
|
+
</Typography>
|
|
115
162
|
</MenuListItem>
|
|
116
163
|
</Menu>
|
|
117
164
|
</Stack>
|
|
@@ -133,3 +180,38 @@ const addComponentToPage = ( model: DropElementParams[ 'model' ] ) => {
|
|
|
133
180
|
options: { ...options, useHistory: false, scrollIntoView: true },
|
|
134
181
|
} );
|
|
135
182
|
};
|
|
183
|
+
|
|
184
|
+
const validateComponentTitle = ( newTitle: string ) => {
|
|
185
|
+
const result = validateComponentName( newTitle );
|
|
186
|
+
|
|
187
|
+
if ( ! result.errorMessage ) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result.errorMessage;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const Indicator = styled( Box, {
|
|
195
|
+
shouldForwardProp: ( prop ) => prop !== 'isActive' && prop !== 'isError',
|
|
196
|
+
} )( ( { theme, isActive, isError } ) => ( {
|
|
197
|
+
display: 'flex',
|
|
198
|
+
width: '100%',
|
|
199
|
+
flexGrow: 1,
|
|
200
|
+
borderRadius: theme.spacing( 0.5 ),
|
|
201
|
+
border: getIndicatorBorder( { isActive, isError, theme } ),
|
|
202
|
+
padding: `0 ${ theme.spacing( 1 ) }`,
|
|
203
|
+
marginLeft: isActive ? theme.spacing( 1 ) : 0,
|
|
204
|
+
minWidth: 0,
|
|
205
|
+
} ) );
|
|
206
|
+
|
|
207
|
+
const getIndicatorBorder = ( { isActive, isError, theme }: { isActive: boolean; isError: boolean; theme: Theme } ) => {
|
|
208
|
+
if ( isError ) {
|
|
209
|
+
return `2px solid ${ theme.palette.error.main }`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if ( isActive ) {
|
|
213
|
+
return `2px solid ${ theme.palette.secondary.main }`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return 'none';
|
|
217
|
+
};
|
|
@@ -4,6 +4,7 @@ import { Box, Divider, Icon, Link, List, Stack, Typography } from '@elementor/ui
|
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
6
|
import { useComponents } from '../../hooks/use-components';
|
|
7
|
+
import { renameComponent } from '../../store/actions/rename-component';
|
|
7
8
|
import { ComponentItem } from './components-item';
|
|
8
9
|
import { LoadingComponents } from './loading-components';
|
|
9
10
|
import { useSearch } from './search-provider';
|
|
@@ -25,7 +26,13 @@ export function ComponentsList() {
|
|
|
25
26
|
return (
|
|
26
27
|
<List sx={ { display: 'flex', flexDirection: 'column', gap: 1, px: 2 } }>
|
|
27
28
|
{ components.map( ( component ) => (
|
|
28
|
-
<ComponentItem
|
|
29
|
+
<ComponentItem
|
|
30
|
+
key={ component.uid }
|
|
31
|
+
component={ component }
|
|
32
|
+
renameComponent={ ( newName ) => {
|
|
33
|
+
renameComponent( component.uid, newName );
|
|
34
|
+
} }
|
|
35
|
+
/>
|
|
29
36
|
) ) }
|
|
30
37
|
</List>
|
|
31
38
|
);
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { z } from '@elementor/schema';
|
|
2
2
|
import { __ } from '@wordpress/i18n';
|
|
3
3
|
|
|
4
|
-
const MIN_NAME_LENGTH = 2;
|
|
4
|
+
export const MIN_NAME_LENGTH = 2;
|
|
5
5
|
const MAX_NAME_LENGTH = 50;
|
|
6
6
|
|
|
7
|
+
const baseComponentSchema = z
|
|
8
|
+
.string()
|
|
9
|
+
.trim()
|
|
10
|
+
.max( MAX_NAME_LENGTH, __( 'Component name is too long. Please keep it under 50 characters.', 'elementor' ) );
|
|
11
|
+
|
|
7
12
|
export const createBaseComponentSchema = ( existingNames: string[] ) => {
|
|
8
13
|
return z.object( {
|
|
9
|
-
componentName:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.max(
|
|
13
|
-
MAX_NAME_LENGTH,
|
|
14
|
-
__( 'Component name is too long. Please keep it under 50 characters.', 'elementor' )
|
|
15
|
-
)
|
|
16
|
-
.refine( ( value ) => ! existingNames.includes( value ), {
|
|
17
|
-
message: __( 'Component name already exists', 'elementor' ),
|
|
18
|
-
} ),
|
|
14
|
+
componentName: baseComponentSchema.refine( ( value ) => ! existingNames.includes( value ), {
|
|
15
|
+
message: __( 'Component name already exists', 'elementor' ),
|
|
16
|
+
} ),
|
|
19
17
|
} );
|
|
20
18
|
};
|
|
21
19
|
|
|
@@ -73,15 +73,45 @@ function getUpdatedComponentPath( path: ComponentsPathItem[], nextDocument: V1Do
|
|
|
73
73
|
return path.slice( 0, componentIndex + 1 );
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
const instanceId = nextDocument?.container.view?.el?.dataset.id;
|
|
77
|
+
const instanceTitle = getInstanceTitle( instanceId, path );
|
|
78
|
+
|
|
76
79
|
return [
|
|
77
80
|
...path,
|
|
78
81
|
{
|
|
79
|
-
instanceId
|
|
82
|
+
instanceId,
|
|
83
|
+
instanceTitle,
|
|
80
84
|
componentId: nextDocument.id,
|
|
81
85
|
},
|
|
82
86
|
];
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
function getInstanceTitle( instanceId: string | undefined, path: ComponentsPathItem[] ): string | undefined {
|
|
90
|
+
if ( ! instanceId ) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const documentsManager = getV1DocumentsManager();
|
|
95
|
+
const parentDocId = path.at( -1 )?.componentId ?? documentsManager.getInitialId();
|
|
96
|
+
const parentDoc = documentsManager.get( parentDocId );
|
|
97
|
+
|
|
98
|
+
type EditorSettings = { title?: string };
|
|
99
|
+
type ContainerWithChildren = V1Element & {
|
|
100
|
+
children?: {
|
|
101
|
+
findRecursive?: ( predicate: ( child: V1Element ) => boolean ) => V1Element | undefined;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const parentContainer = parentDoc?.container as unknown as ContainerWithChildren | undefined;
|
|
106
|
+
const widget = parentContainer?.children?.findRecursive?.(
|
|
107
|
+
( container: V1Element ) => container.id === instanceId
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const editorSettings = widget?.model?.get?.( 'editor_settings' ) as EditorSettings | undefined;
|
|
111
|
+
|
|
112
|
+
return editorSettings?.title;
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
function getComponentDOMElement( id: V1Document[ 'id' ] | undefined ) {
|
|
86
116
|
if ( ! id ) {
|
|
87
117
|
return null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type BackboneModel,
|
|
3
|
+
type BackboneModelConstructor,
|
|
3
4
|
type CreateTemplatedElementTypeOptions,
|
|
4
5
|
createTemplatedElementView,
|
|
5
6
|
type ElementModel,
|
|
@@ -8,10 +9,12 @@ import {
|
|
|
8
9
|
type LegacyWindow,
|
|
9
10
|
} from '@elementor/editor-canvas';
|
|
10
11
|
import { getCurrentDocument } from '@elementor/editor-documents';
|
|
12
|
+
import { __getState as getState } from '@elementor/store';
|
|
11
13
|
import { __ } from '@wordpress/i18n';
|
|
12
14
|
|
|
13
15
|
import { apiClient } from './api';
|
|
14
16
|
import { type ComponentInstanceProp } from './prop-types/component-instance-prop-type';
|
|
17
|
+
import { type ComponentsSlice, selectComponentByUid } from './store/store';
|
|
15
18
|
import { type ExtendedWindow } from './types';
|
|
16
19
|
import { switchToComponent } from './utils/switch-to-component';
|
|
17
20
|
import { trackComponentEvent } from './utils/tracking';
|
|
@@ -36,6 +39,18 @@ type ContextMenuGroup = {
|
|
|
36
39
|
actions: ContextMenuAction[];
|
|
37
40
|
};
|
|
38
41
|
|
|
42
|
+
type ComponentModel = ElementModel & {
|
|
43
|
+
componentId?: number | string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type ComponentModelInstance = BackboneModel< ComponentModel > & {
|
|
47
|
+
trigger: ( event: string, ...args: unknown[] ) => void;
|
|
48
|
+
getTitle: () => string;
|
|
49
|
+
getComponentId: () => number | null;
|
|
50
|
+
getComponentName: () => string;
|
|
51
|
+
getComponentUid: () => string | null;
|
|
52
|
+
};
|
|
53
|
+
|
|
39
54
|
export const COMPONENT_WIDGET_TYPE = 'e-component';
|
|
40
55
|
|
|
41
56
|
const updateGroups = ( groups: ContextMenuGroup[], config: ContextMenuGroupConfig ): ContextMenuGroup[] => {
|
|
@@ -64,8 +79,9 @@ export function createComponentType(
|
|
|
64
79
|
options: CreateTemplatedElementTypeOptions & { showLockedByModal?: ( lockedBy: string ) => void }
|
|
65
80
|
): typeof ElementType {
|
|
66
81
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
82
|
+
const WidgetType = legacyWindow.elementor.modules.elements.types.Widget;
|
|
67
83
|
|
|
68
|
-
return class extends
|
|
84
|
+
return class extends WidgetType {
|
|
69
85
|
getType() {
|
|
70
86
|
return options.type;
|
|
71
87
|
}
|
|
@@ -73,6 +89,10 @@ export function createComponentType(
|
|
|
73
89
|
getView() {
|
|
74
90
|
return createComponentView( { ...options } );
|
|
75
91
|
}
|
|
92
|
+
|
|
93
|
+
getModel(): BackboneModelConstructor< ComponentModel > {
|
|
94
|
+
return createComponentModel();
|
|
95
|
+
}
|
|
76
96
|
};
|
|
77
97
|
}
|
|
78
98
|
|
|
@@ -253,3 +273,67 @@ function setInactiveRecursively( model: BackboneModel< ElementModel > ) {
|
|
|
253
273
|
} );
|
|
254
274
|
}
|
|
255
275
|
}
|
|
276
|
+
|
|
277
|
+
function createComponentModel(): BackboneModelConstructor< ComponentModel > {
|
|
278
|
+
const legacyWindow = window as unknown as LegacyWindow;
|
|
279
|
+
const WidgetType = legacyWindow.elementor.modules.elements.types.Widget;
|
|
280
|
+
const widgetTypeInstance = new WidgetType() as unknown as BackboneModelConstructor< ElementModel >;
|
|
281
|
+
const BaseWidgetModel = widgetTypeInstance.getModel();
|
|
282
|
+
|
|
283
|
+
return BaseWidgetModel.extend( {
|
|
284
|
+
initialize( this: ComponentModelInstance, attributes: unknown, options: unknown ): void {
|
|
285
|
+
BaseWidgetModel.prototype.initialize.call( this, attributes, options );
|
|
286
|
+
|
|
287
|
+
const componentInstance = this.get( 'settings' )?.get( 'component_instance' ) as
|
|
288
|
+
| ComponentInstanceProp
|
|
289
|
+
| undefined;
|
|
290
|
+
if ( componentInstance?.value ) {
|
|
291
|
+
const componentId = componentInstance.value.component_id?.value;
|
|
292
|
+
if ( componentId && typeof componentId === 'number' ) {
|
|
293
|
+
this.set( 'componentId', componentId );
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
getTitle( this: ComponentModelInstance ): string {
|
|
299
|
+
const editorSettings = this.get( 'editor_settings' ) as
|
|
300
|
+
| {
|
|
301
|
+
title?: string;
|
|
302
|
+
component_uid?: string;
|
|
303
|
+
}
|
|
304
|
+
| undefined;
|
|
305
|
+
|
|
306
|
+
const instanceTitle = editorSettings?.title;
|
|
307
|
+
if ( instanceTitle ) {
|
|
308
|
+
return instanceTitle;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const componentUid = editorSettings?.component_uid;
|
|
312
|
+
if ( componentUid ) {
|
|
313
|
+
const component = selectComponentByUid( getState() as ComponentsSlice, componentUid );
|
|
314
|
+
if ( component?.name ) {
|
|
315
|
+
return component.name;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return ( window as unknown as LegacyWindow ).elementor.getElementData( this ).title;
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
getComponentId( this: ComponentModelInstance ): number | null {
|
|
323
|
+
return ( this.get( 'componentId' ) as number | undefined ) || null;
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
getComponentName( this: ComponentModelInstance ): string {
|
|
327
|
+
return this.getTitle();
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
getComponentUid( this: ComponentModelInstance ): string | null {
|
|
331
|
+
const editorSettings = this.get( 'editor_settings' ) as
|
|
332
|
+
| {
|
|
333
|
+
component_uid?: string;
|
|
334
|
+
}
|
|
335
|
+
| undefined;
|
|
336
|
+
return editorSettings?.component_uid || null;
|
|
337
|
+
},
|
|
338
|
+
} );
|
|
339
|
+
}
|
package/src/store/store.ts
CHANGED
|
@@ -30,12 +30,14 @@ type ComponentsState = {
|
|
|
30
30
|
archivedData: PublishedComponent[];
|
|
31
31
|
path: ComponentsPathItem[];
|
|
32
32
|
currentComponentId: V1Document[ 'id' ] | null;
|
|
33
|
+
updatedComponentNames: Record< number, string >;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
export type ComponentsSlice = SliceState< typeof slice >;
|
|
36
37
|
|
|
37
38
|
export type ComponentsPathItem = {
|
|
38
39
|
instanceId?: string;
|
|
40
|
+
instanceTitle?: string;
|
|
39
41
|
componentId: V1Document[ 'id' ];
|
|
40
42
|
};
|
|
41
43
|
|
|
@@ -48,6 +50,7 @@ export const initialState: ComponentsState = {
|
|
|
48
50
|
archivedData: [],
|
|
49
51
|
path: [],
|
|
50
52
|
currentComponentId: null,
|
|
53
|
+
updatedComponentNames: {},
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
export const SLICE_NAME = 'components';
|
|
@@ -111,6 +114,20 @@ export const slice = createSlice( {
|
|
|
111
114
|
|
|
112
115
|
component.overridableProps = payload.overridableProps;
|
|
113
116
|
},
|
|
117
|
+
rename: ( state, { payload }: PayloadAction< { componentUid: string; name: string } > ) => {
|
|
118
|
+
const component = state.data.find( ( comp ) => comp.uid === payload.componentUid );
|
|
119
|
+
|
|
120
|
+
if ( ! component ) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if ( component.id ) {
|
|
124
|
+
state.updatedComponentNames[ component.id ] = payload.name;
|
|
125
|
+
}
|
|
126
|
+
component.name = payload.name;
|
|
127
|
+
},
|
|
128
|
+
cleanUpdatedComponentNames: ( state ) => {
|
|
129
|
+
state.updatedComponentNames = {};
|
|
130
|
+
},
|
|
114
131
|
},
|
|
115
132
|
extraReducers: ( builder ) => {
|
|
116
133
|
builder.addCase( loadComponents.fulfilled, ( state, { payload }: PayloadAction< GetComponentResponse > ) => {
|
|
@@ -140,6 +157,10 @@ export const useComponent = ( componentId: ComponentId | null ) => {
|
|
|
140
157
|
return useSelector( ( state: ComponentsSlice ) => ( componentId ? selectComponent( state, componentId ) : null ) );
|
|
141
158
|
};
|
|
142
159
|
|
|
160
|
+
export const selectComponentByUid = ( state: ComponentsSlice, componentUid: string ) =>
|
|
161
|
+
state[ SLICE_NAME ].data.find( ( component ) => component.uid === componentUid ) ??
|
|
162
|
+
state[ SLICE_NAME ].unpublishedData.find( ( component ) => component.uid === componentUid );
|
|
163
|
+
|
|
143
164
|
export const selectComponents = createSelector(
|
|
144
165
|
selectData,
|
|
145
166
|
selectUnpublishedData,
|
|
@@ -209,3 +230,11 @@ export const selectArchivedComponents = createSelector(
|
|
|
209
230
|
selectArchivedData,
|
|
210
231
|
( archivedData: PublishedComponent[] ) => archivedData
|
|
211
232
|
);
|
|
233
|
+
export const selectUpdatedComponentNames = createSelector(
|
|
234
|
+
( state: ComponentsSlice ) => state[ SLICE_NAME ].updatedComponentNames,
|
|
235
|
+
( updatedComponentNames ) =>
|
|
236
|
+
Object.entries( updatedComponentNames ).map( ( [ componentId, title ] ) => ( {
|
|
237
|
+
componentId: Number( componentId ),
|
|
238
|
+
title,
|
|
239
|
+
} ) )
|
|
240
|
+
);
|
package/src/sync/before-save.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { type DocumentSaveStatus } from '../types';
|
|
|
5
5
|
import { createComponentsBeforeSave } from './create-components-before-save';
|
|
6
6
|
import { setComponentOverridablePropsSettingsBeforeSave } from './set-component-overridable-props-settings-before-save';
|
|
7
7
|
import { updateArchivedComponentBeforeSave } from './update-archived-component-before-save';
|
|
8
|
+
import { updateComponentTitleBeforeSave } from './update-component-title-before-save';
|
|
8
9
|
import { updateComponentsBeforeSave } from './update-components-before-save';
|
|
9
10
|
|
|
10
11
|
type Options = {
|
|
@@ -27,5 +28,6 @@ export const beforeSave = ( { container, status }: Options ) => {
|
|
|
27
28
|
createComponentsBeforeSave( { elements, status } ),
|
|
28
29
|
updateComponentsBeforeSave( { elements, status } ),
|
|
29
30
|
setComponentOverridablePropsSettingsBeforeSave( { container } ),
|
|
31
|
+
updateComponentTitleBeforeSave(),
|
|
30
32
|
] );
|
|
31
33
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { __dispatch as dispatch, __getState as getState } from '@elementor/store';
|
|
2
|
+
|
|
3
|
+
import { apiClient } from '../api';
|
|
4
|
+
import { selectUpdatedComponentNames, slice } from '../store/store';
|
|
5
|
+
|
|
6
|
+
export const updateComponentTitleBeforeSave = async () => {
|
|
7
|
+
const updatedComponentNames = selectUpdatedComponentNames( getState() );
|
|
8
|
+
|
|
9
|
+
if ( ! updatedComponentNames.length ) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const result = await apiClient.updateComponentTitle( updatedComponentNames );
|
|
14
|
+
|
|
15
|
+
if ( result.failedIds.length === 0 ) {
|
|
16
|
+
dispatch( slice.actions.cleanUpdatedComponentNames() );
|
|
17
|
+
}
|
|
18
|
+
};
|