@elementor/editor-site-navigation 0.6.2 → 0.8.0
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/CHANGELOG.md +22 -0
- package/README.md +48 -0
- package/dist/index.js +338 -0
- package/dist/index.mjs +347 -0
- package/package.json +7 -6
- package/src/components/panel/actions-menu/action-list-item.tsx +27 -0
- package/src/components/panel/actions-menu/action-menu-item.tsx +11 -0
- package/src/components/panel/actions-menu/page-actions-menu.tsx +29 -0
- package/src/components/panel/pages-actions/delete.tsx +21 -0
- package/src/components/panel/pages-actions/duplicate.tsx +14 -0
- package/src/components/panel/pages-actions/rename.tsx +14 -0
- package/src/components/panel/pages-actions/set-home.tsx +16 -0
- package/src/components/panel/pages-actions/view.tsx +14 -0
- package/src/components/panel/pages-list/__tests__/page-list-item.test.tsx +110 -0
- package/src/components/panel/pages-list/__tests__/pages-collapsible-list.test.tsx +48 -0
- package/src/components/panel/pages-list/collapsible-list.tsx +63 -0
- package/src/components/panel/pages-list/page-list-item.tsx +66 -0
- package/src/components/panel/pages-list/pages-collapsible-list.tsx +24 -0
- package/src/components/panel/shell.tsx +58 -0
- package/src/components/shared/page-title-and-status.tsx +50 -0
- package/src/hooks/types/interfaces.ts +13 -0
- package/src/hooks/use-post-types.ts +64 -0
- package/src/hooks/use-posts.ts +123 -0
- package/src/init.ts +12 -0
- package/src/types.ts +7 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { Page } from '../../../../types';
|
|
4
|
+
import PagesCollapsibleList from '../pages-collapsible-list';
|
|
5
|
+
|
|
6
|
+
jest.mock( '@elementor/editor-documents' );
|
|
7
|
+
|
|
8
|
+
describe( '@elementor/editor-site-navigation - PagesCollapsibleList', () => {
|
|
9
|
+
const mockPosts: Page[] = [
|
|
10
|
+
{ id: 1, type: 'page', title: 'Home', status: 'draft' },
|
|
11
|
+
{ id: 2, type: 'page', title: 'About', status: 'publish' },
|
|
12
|
+
{ id: 3, type: 'page', title: 'Services', status: 'publish', isHome: true },
|
|
13
|
+
{ id: 4, type: 'page', title: 'Contact', status: 'draft' },
|
|
14
|
+
{ id: 5, type: 'page', title: 'FAQ', status: 'publish' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
afterAll( () => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
} );
|
|
20
|
+
|
|
21
|
+
it( 'should render closed list', () => {
|
|
22
|
+
// Act.
|
|
23
|
+
const { getByText, queryByText } = render(
|
|
24
|
+
<PagesCollapsibleList pages={ mockPosts } isOpenByDefault={ false } />,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Assert.
|
|
28
|
+
const label = getByText( `Pages (${ mockPosts.length })` );
|
|
29
|
+
expect( label ).toBeInTheDocument();
|
|
30
|
+
|
|
31
|
+
const postInList = queryByText( 'Services' );
|
|
32
|
+
expect( postInList ).toBeNull();
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
it( 'should render open list', () => {
|
|
36
|
+
// Act.
|
|
37
|
+
const { getByText } = render(
|
|
38
|
+
<PagesCollapsibleList pages={ mockPosts } isOpenByDefault={ true } />,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Assert.
|
|
42
|
+
const label = getByText( `Pages (${ mockPosts.length })` );
|
|
43
|
+
expect( label ).toBeInTheDocument();
|
|
44
|
+
|
|
45
|
+
const postInList = getByText( 'Services' );
|
|
46
|
+
expect( postInList ).toBeInTheDocument();
|
|
47
|
+
} );
|
|
48
|
+
} );
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ComponentType, PropsWithChildren, useState } from 'react';
|
|
3
|
+
import { Collapse, IconButton, List, ListItem, ListItemIcon, ListItemText, styled, SvgIconProps } from '@elementor/ui';
|
|
4
|
+
import { ChevronDownIcon } from '@elementor/icons';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
label: string
|
|
8
|
+
Icon: ComponentType;
|
|
9
|
+
isOpenByDefault?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface RotateIconProps extends SvgIconProps {
|
|
13
|
+
isOpen: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// TODO 21/06/2023 : Should replace this with future Rotate component that will be implemented in elementor-ui
|
|
17
|
+
const RotateIcon = styled( ChevronDownIcon, {
|
|
18
|
+
shouldForwardProp: ( prop ) => prop !== 'isOpen',
|
|
19
|
+
} )<RotateIconProps>( ( { theme, isOpen } ) => ( {
|
|
20
|
+
transform: isOpen ? 'rotate(0deg)' : 'rotate(-90deg)',
|
|
21
|
+
transition: theme.transitions.create( 'transform', {
|
|
22
|
+
duration: theme.transitions.duration.standard,
|
|
23
|
+
} ),
|
|
24
|
+
} ) );
|
|
25
|
+
|
|
26
|
+
export default function CollapsibleList(
|
|
27
|
+
{
|
|
28
|
+
label,
|
|
29
|
+
Icon,
|
|
30
|
+
isOpenByDefault = false,
|
|
31
|
+
children,
|
|
32
|
+
}: PropsWithChildren<Props>
|
|
33
|
+
) {
|
|
34
|
+
const [ isOpen, setIsOpen ] = useState( isOpenByDefault );
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<ListItem disableGutters>
|
|
39
|
+
<ListItemIcon>
|
|
40
|
+
<IconButton
|
|
41
|
+
onClick={ () => setIsOpen( ( prev ) => ! prev ) }
|
|
42
|
+
sx={ { color: 'inherit' } }
|
|
43
|
+
size="small"
|
|
44
|
+
>
|
|
45
|
+
<RotateIcon fontSize="small" isOpen={ isOpen } />
|
|
46
|
+
</IconButton>
|
|
47
|
+
</ListItemIcon>
|
|
48
|
+
<ListItemIcon>
|
|
49
|
+
<Icon />
|
|
50
|
+
</ListItemIcon>
|
|
51
|
+
<ListItemText>{ label }</ListItemText>
|
|
52
|
+
</ListItem>
|
|
53
|
+
<Collapse
|
|
54
|
+
in={ isOpen }
|
|
55
|
+
timeout="auto"
|
|
56
|
+
unmountOnExit>
|
|
57
|
+
<List dense>
|
|
58
|
+
{ children }
|
|
59
|
+
</List>
|
|
60
|
+
</Collapse>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
bindMenu,
|
|
4
|
+
bindTrigger,
|
|
5
|
+
ListItem,
|
|
6
|
+
ListItemButton,
|
|
7
|
+
ListItemIcon,
|
|
8
|
+
ListItemText,
|
|
9
|
+
ToggleButton,
|
|
10
|
+
usePopupState,
|
|
11
|
+
} from '@elementor/ui';
|
|
12
|
+
import { DotsVerticalIcon, HomeIcon } from '@elementor/icons';
|
|
13
|
+
import { Page } from '../../../types';
|
|
14
|
+
import { useActiveDocument, useNavigateToDocument } from '@elementor/editor-documents';
|
|
15
|
+
import PageTitleAndStatus from '../../shared/page-title-and-status';
|
|
16
|
+
import PageActionsMenu from '../actions-menu/page-actions-menu';
|
|
17
|
+
|
|
18
|
+
export default function PageListItem( { page }: { page: Page } ) {
|
|
19
|
+
const popupState = usePopupState( {
|
|
20
|
+
variant: 'popover',
|
|
21
|
+
popupId: 'page-actions',
|
|
22
|
+
} );
|
|
23
|
+
|
|
24
|
+
const activeDocument = useActiveDocument();
|
|
25
|
+
const navigateToDocument = useNavigateToDocument();
|
|
26
|
+
|
|
27
|
+
const isActive = activeDocument?.id === page.id;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<ListItem
|
|
32
|
+
disablePadding
|
|
33
|
+
secondaryAction={
|
|
34
|
+
<ToggleButton
|
|
35
|
+
value
|
|
36
|
+
color="secondary"
|
|
37
|
+
size="small"
|
|
38
|
+
selected={ popupState.isOpen }
|
|
39
|
+
{ ...bindTrigger( popupState ) }
|
|
40
|
+
>
|
|
41
|
+
<DotsVerticalIcon fontSize="small" />
|
|
42
|
+
</ToggleButton>
|
|
43
|
+
}
|
|
44
|
+
>
|
|
45
|
+
<ListItemButton
|
|
46
|
+
selected={ isActive }
|
|
47
|
+
onClick={ () => navigateToDocument( page.id ) }
|
|
48
|
+
dense
|
|
49
|
+
>
|
|
50
|
+
<ListItemIcon />
|
|
51
|
+
<ListItemText
|
|
52
|
+
disableTypography={ true }
|
|
53
|
+
>
|
|
54
|
+
<PageTitleAndStatus page={ page } />
|
|
55
|
+
</ListItemText>
|
|
56
|
+
{ page.isHome &&
|
|
57
|
+
<ListItemIcon>
|
|
58
|
+
<HomeIcon color="disabled" />
|
|
59
|
+
</ListItemIcon>
|
|
60
|
+
}
|
|
61
|
+
</ListItemButton>
|
|
62
|
+
</ListItem>
|
|
63
|
+
<PageActionsMenu page={ page } { ...bindMenu( popupState ) } />
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import CollapsibleList from './collapsible-list';
|
|
3
|
+
import { Page } from '../../../types';
|
|
4
|
+
import { PageTypeIcon } from '@elementor/icons';
|
|
5
|
+
import PageListItem from './page-list-item';
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
pages: Page[];
|
|
9
|
+
isOpenByDefault?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function PagesCollapsibleList( { pages, isOpenByDefault = false }: Props ) {
|
|
13
|
+
const label = `Pages (${ pages.length })`; // TODO 21/06/2023 : This label should come from the backend
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<CollapsibleList
|
|
17
|
+
label={ label }
|
|
18
|
+
Icon={ PageTypeIcon }
|
|
19
|
+
isOpenByDefault={ isOpenByDefault }
|
|
20
|
+
>
|
|
21
|
+
{ pages.map( ( page ) => <PageListItem key={ page.id } page={ page } /> ) }
|
|
22
|
+
</CollapsibleList>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Button, Divider, Grid, List, Paper, ThemeProvider, Typography } from '@elementor/ui';
|
|
3
|
+
import { PlusIcon } from '@elementor/icons';
|
|
4
|
+
import { Page } from '../../types';
|
|
5
|
+
import PagesCollapsibleList from './pages-list/pages-collapsible-list';
|
|
6
|
+
|
|
7
|
+
// TODO: Remove once connected to real data fetching.
|
|
8
|
+
const mockPages: Page[] = [
|
|
9
|
+
{
|
|
10
|
+
id: 1,
|
|
11
|
+
type: 'page',
|
|
12
|
+
title: 'This is a very long title that somebody wrote, a very very long line',
|
|
13
|
+
status: 'pending approval',
|
|
14
|
+
},
|
|
15
|
+
{ id: 2, type: 'page', title: 'About', status: 'publish' },
|
|
16
|
+
{ id: 3, type: 'page', title: 'Services', status: 'publish', isHome: true },
|
|
17
|
+
{ id: 4, type: 'page', title: 'Contact', status: 'draft' },
|
|
18
|
+
{ id: 5, type: 'page', title: 'FAQ', status: 'publish' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const Shell = () => {
|
|
22
|
+
return (
|
|
23
|
+
<ThemeProvider colorScheme="light">
|
|
24
|
+
<Box sx={ { width: '100%', maxWidth: 360 } }>
|
|
25
|
+
<Paper>
|
|
26
|
+
<Grid
|
|
27
|
+
container
|
|
28
|
+
justifyContent="center"
|
|
29
|
+
alignItems="center"
|
|
30
|
+
sx={ { height: 51 } }>
|
|
31
|
+
<Typography variant="h6">Pages</Typography>
|
|
32
|
+
</Grid>
|
|
33
|
+
<Divider />
|
|
34
|
+
<Box
|
|
35
|
+
display="flex"
|
|
36
|
+
justifyContent="flex-end"
|
|
37
|
+
alignItems="center"
|
|
38
|
+
>
|
|
39
|
+
<Button
|
|
40
|
+
sx={ { mt: 4, mb: 4, mr: 5 } }
|
|
41
|
+
startIcon={ <PlusIcon /> }
|
|
42
|
+
>
|
|
43
|
+
Add New
|
|
44
|
+
</Button>
|
|
45
|
+
</Box>
|
|
46
|
+
<Box sx={ { width: '100%', maxWidth: 360 } }>
|
|
47
|
+
<List dense>
|
|
48
|
+
<PagesCollapsibleList pages={ mockPages } isOpenByDefault={ true } />
|
|
49
|
+
</List>
|
|
50
|
+
</Box>
|
|
51
|
+
<Divider />
|
|
52
|
+
</Paper>
|
|
53
|
+
</Box>
|
|
54
|
+
</ThemeProvider>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default Shell;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Box, Typography } from '@elementor/ui';
|
|
3
|
+
import { Page } from '../../types';
|
|
4
|
+
import { useReverseHtmlEntities } from '../../hooks/use-reverse-html-entities';
|
|
5
|
+
|
|
6
|
+
const PageStatus = ( { status }: { status: string } ) => {
|
|
7
|
+
if ( 'publish' === status ) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Typography
|
|
13
|
+
component="span"
|
|
14
|
+
variant="body1"
|
|
15
|
+
sx={ {
|
|
16
|
+
textTransform: 'capitalize',
|
|
17
|
+
fontStyle: 'italic',
|
|
18
|
+
whiteSpace: 'nowrap',
|
|
19
|
+
flexBasis: 'content',
|
|
20
|
+
} }
|
|
21
|
+
>
|
|
22
|
+
({ status })
|
|
23
|
+
</Typography>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const PageTitle = ( { title }: { title: string } ) => {
|
|
28
|
+
const modifiedTitle = useReverseHtmlEntities( title );
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Typography
|
|
32
|
+
component="span"
|
|
33
|
+
variant="body1"
|
|
34
|
+
noWrap
|
|
35
|
+
sx={ {
|
|
36
|
+
flexBasis: 'auto',
|
|
37
|
+
} }
|
|
38
|
+
>
|
|
39
|
+
{ modifiedTitle }
|
|
40
|
+
</Typography>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default function PageTitleAndStatus( { page }: { page: Page } ) {
|
|
45
|
+
return (
|
|
46
|
+
<Box display="flex">
|
|
47
|
+
<PageTitle title={ page.title } /> <PageStatus status={ page.status } />
|
|
48
|
+
</Box>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { PostType } from './types/interfaces';
|
|
3
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
4
|
+
|
|
5
|
+
type PostTypesResponse = Record<string, PostType>;
|
|
6
|
+
|
|
7
|
+
// allowedPostTypes is only used to filter irrelevant types when fetching all post types.
|
|
8
|
+
// You can still use a specific post type name to fetch a post type not present in allowedPostTypes.
|
|
9
|
+
const allowedPostTypes = [ 'page' ];
|
|
10
|
+
|
|
11
|
+
const useFetchPostTypes = (
|
|
12
|
+
postTypeName?: string
|
|
13
|
+
): ( PostType | null )[] => {
|
|
14
|
+
const [ postTypes, setPostTypes ] = useState<( PostType | null )[]>( [] );
|
|
15
|
+
|
|
16
|
+
useEffect( () => {
|
|
17
|
+
const fetchPostTypes = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const response: PostTypesResponse = await apiFetch( {
|
|
20
|
+
path: '/wp/v2/types',
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
if ( postTypeName ) {
|
|
24
|
+
const specificPostType = response[ postTypeName ];
|
|
25
|
+
|
|
26
|
+
setPostTypes( [
|
|
27
|
+
specificPostType
|
|
28
|
+
? { ...specificPostType }
|
|
29
|
+
: null,
|
|
30
|
+
] );
|
|
31
|
+
} else {
|
|
32
|
+
const filteredResponse: PostTypesResponse = {};
|
|
33
|
+
|
|
34
|
+
allowedPostTypes.forEach( ( type ) => {
|
|
35
|
+
if ( response[ type ] ) {
|
|
36
|
+
filteredResponse[ type ] = response[ type ];
|
|
37
|
+
}
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
const filteredPostTypes: ( PostType | null )[] = Object.keys( filteredResponse ).map(
|
|
41
|
+
( key ) => ( { ...filteredResponse[ key ] } )
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
setPostTypes( filteredPostTypes );
|
|
45
|
+
}
|
|
46
|
+
} catch ( error ) {
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.error( 'Error fetching post types:', error );
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
fetchPostTypes();
|
|
53
|
+
}, [ postTypeName ] );
|
|
54
|
+
|
|
55
|
+
return postTypes;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const usePostTypes = (
|
|
59
|
+
postTypeName?: string
|
|
60
|
+
): ( PostType | null )[] => {
|
|
61
|
+
const postTypes = useFetchPostTypes( postTypeName );
|
|
62
|
+
|
|
63
|
+
return useMemo( () => postTypes, [ postTypes ] );
|
|
64
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState, useCallback } from 'react';
|
|
2
|
+
import { usePostTypes } from './use-post-types';
|
|
3
|
+
import { Post, PostType } from './types/interfaces';
|
|
4
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
5
|
+
|
|
6
|
+
const defaultRestNamespace = 'wp/v2';
|
|
7
|
+
|
|
8
|
+
const useFetchPosts = ( postType: PostType | null | undefined ): [ Post[], () => void, boolean ] => {
|
|
9
|
+
const [ posts, setPosts ] = useState<Post[]>( [] );
|
|
10
|
+
const [ isLoading, setIsLoading ] = useState( false );
|
|
11
|
+
|
|
12
|
+
const fetchPosts = useCallback( () => {
|
|
13
|
+
setIsLoading( true );
|
|
14
|
+
|
|
15
|
+
const path = postType
|
|
16
|
+
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
17
|
+
: `/${ defaultRestNamespace }/posts`;
|
|
18
|
+
|
|
19
|
+
apiFetch( { path } )
|
|
20
|
+
.then( ( response ) => {
|
|
21
|
+
if ( Array.isArray( response ) ) {
|
|
22
|
+
setPosts( response );
|
|
23
|
+
}
|
|
24
|
+
} )
|
|
25
|
+
.catch( ( error ) => {
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
console.error( 'Error fetching posts:', error );
|
|
28
|
+
} )
|
|
29
|
+
.finally( () => {
|
|
30
|
+
setIsLoading( false );
|
|
31
|
+
} );
|
|
32
|
+
}, [ postType ] );
|
|
33
|
+
|
|
34
|
+
useEffect( () => {
|
|
35
|
+
fetchPosts();
|
|
36
|
+
}, [ fetchPosts ] );
|
|
37
|
+
|
|
38
|
+
return [ posts, fetchPosts, isLoading ];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const useUpdatePost = ( postType: PostType | null | undefined ): ( ( postId: number, updatedPost: Partial<Post> ) => void ) => {
|
|
42
|
+
return ( postId: number, updatedPost: Partial<Post> ) => {
|
|
43
|
+
const path = postType
|
|
44
|
+
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
45
|
+
: `/${ defaultRestNamespace }/posts`;
|
|
46
|
+
|
|
47
|
+
apiFetch( {
|
|
48
|
+
path: `/${ path }/${ postId }`,
|
|
49
|
+
method: 'POST',
|
|
50
|
+
data: updatedPost,
|
|
51
|
+
} )
|
|
52
|
+
.catch( ( error ) => {
|
|
53
|
+
// eslint-disable-next-line no-console
|
|
54
|
+
console.error( 'Error updating post:', error );
|
|
55
|
+
} );
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const useCreatePost = ( postType: PostType | null | undefined ): ( ( newPost: Partial<Post> ) => void ) => {
|
|
60
|
+
return ( newPost: Partial<Post> ) => {
|
|
61
|
+
const path = postType
|
|
62
|
+
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
63
|
+
: `/${ defaultRestNamespace }/posts`;
|
|
64
|
+
|
|
65
|
+
apiFetch( {
|
|
66
|
+
path,
|
|
67
|
+
method: 'POST',
|
|
68
|
+
data: newPost,
|
|
69
|
+
} )
|
|
70
|
+
.catch( ( error ) => {
|
|
71
|
+
// eslint-disable-next-line no-console
|
|
72
|
+
console.error( 'Error creating post:', error );
|
|
73
|
+
} );
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const useDeletePost = ( postType: PostType | null | undefined ): ( ( postId: number ) => void ) => {
|
|
78
|
+
return ( postId: number ) => {
|
|
79
|
+
const path = postType
|
|
80
|
+
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
81
|
+
: `/${ defaultRestNamespace }/posts`;
|
|
82
|
+
|
|
83
|
+
apiFetch( {
|
|
84
|
+
path: `/${ path }/${ postId }`,
|
|
85
|
+
method: 'DELETE',
|
|
86
|
+
} )
|
|
87
|
+
.catch( ( error ) => {
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.error( 'Error deleting post:', error );
|
|
90
|
+
} );
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const usePosts = ( postType?: string | null ): {
|
|
95
|
+
posts: Post[];
|
|
96
|
+
getPosts: () => void;
|
|
97
|
+
updatePost: ( postId: number, updatedPost: Partial<Post> ) => void;
|
|
98
|
+
createPost: ( newPost: Partial<Post> ) => void;
|
|
99
|
+
deletePost: ( postId: number ) => void;
|
|
100
|
+
isLoading: boolean;
|
|
101
|
+
} => {
|
|
102
|
+
const postTypes = usePostTypes();
|
|
103
|
+
const postTypeData = postTypes.find( ( type ) => type?.name === postType );
|
|
104
|
+
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
106
|
+
const [ posts, fetchPosts, isLoading ] = useFetchPosts( postTypeData );
|
|
107
|
+
const updatePost = useUpdatePost( postTypeData );
|
|
108
|
+
const createPost = useCreatePost( postTypeData );
|
|
109
|
+
const deletePost = useDeletePost( postTypeData );
|
|
110
|
+
|
|
111
|
+
const getPosts = useMemo( () => {
|
|
112
|
+
return () => posts;
|
|
113
|
+
}, [ posts ] );
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
posts,
|
|
117
|
+
getPosts,
|
|
118
|
+
updatePost,
|
|
119
|
+
createPost,
|
|
120
|
+
deletePost,
|
|
121
|
+
isLoading,
|
|
122
|
+
};
|
|
123
|
+
};
|
package/src/init.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import RecentlyEdited from './components/top-bar/recently-edited';
|
|
2
2
|
import { injectIntoPageIndication } from '@elementor/editor-app-bar';
|
|
3
|
+
import { injectIntoTop } from '@elementor/editor';
|
|
4
|
+
import Shell from './components/panel/shell';
|
|
3
5
|
|
|
4
6
|
export default function init() {
|
|
5
7
|
registerTopBarMenuItems();
|
|
8
|
+
// TODO 06/06/2023 : uncomment registerPanel() when we are ready to release
|
|
9
|
+
registerPanel();
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
function registerTopBarMenuItems() {
|
|
@@ -11,3 +15,11 @@ function registerTopBarMenuItems() {
|
|
|
11
15
|
filler: RecentlyEdited,
|
|
12
16
|
} );
|
|
13
17
|
}
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
20
|
+
function registerPanel() {
|
|
21
|
+
injectIntoTop( {
|
|
22
|
+
id: 'navigation-panel',
|
|
23
|
+
filler: Shell,
|
|
24
|
+
} );
|
|
25
|
+
}
|