@elementor/editor-site-navigation 0.19.9 → 0.19.11

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/index.js +324 -177
  3. package/dist/index.mjs +309 -163
  4. package/package.json +4 -3
  5. package/src/api/recent-posts.ts +15 -0
  6. package/src/components/panel/actions-menu/action-menu-item.tsx +10 -13
  7. package/src/components/panel/actions-menu/actions/__tests__/delete.test.tsx +2 -2
  8. package/src/components/panel/actions-menu/actions/__tests__/set-home.test.tsx +4 -4
  9. package/src/components/panel/actions-menu/actions/__tests__/view.test.tsx +1 -1
  10. package/src/components/panel/actions-menu/actions/delete.tsx +8 -2
  11. package/src/components/panel/actions-menu/actions/duplicate.tsx +1 -1
  12. package/src/components/panel/actions-menu/actions/rename.tsx +1 -1
  13. package/src/components/panel/actions-menu/actions/set-home.tsx +12 -4
  14. package/src/components/panel/actions-menu/actions/view.tsx +1 -1
  15. package/src/components/panel/add-new-button.tsx +3 -1
  16. package/src/components/panel/error-snackbar.tsx +33 -0
  17. package/src/components/panel/posts-list/__tests__/post-list-item.test.tsx +2 -2
  18. package/src/components/panel/posts-list/__tests__/posts-collapsible-list.test.tsx +4 -4
  19. package/src/components/panel/posts-list/collapsible-list.tsx +22 -10
  20. package/src/components/panel/posts-list/error-state.tsx +37 -0
  21. package/src/components/panel/posts-list/list-items/edit-mode-template.tsx +48 -31
  22. package/src/components/panel/posts-list/list-items/list-item-create.tsx +13 -10
  23. package/src/components/panel/posts-list/list-items/list-item-duplicate.tsx +13 -10
  24. package/src/components/panel/posts-list/list-items/list-item-rename.tsx +25 -11
  25. package/src/components/panel/posts-list/list-items/list-item-view.tsx +8 -13
  26. package/src/components/panel/posts-list/posts-collapsible-list.tsx +31 -3
  27. package/src/components/panel/shell.tsx +5 -1
  28. package/src/components/shared/page-title-and-status.tsx +4 -3
  29. package/src/components/top-bar/__tests__/add-new-page.test.tsx +6 -5
  30. package/src/components/top-bar/__tests__/recently-edited.test.tsx +23 -11
  31. package/src/components/top-bar/post-list-item.tsx +2 -2
  32. package/src/components/top-bar/recently-edited.tsx +11 -1
  33. package/src/contexts/post-list-context.tsx +5 -0
  34. package/src/hooks/__tests__/use-recent-posts.test.ts +8 -9
  35. package/src/hooks/use-posts-actions.ts +2 -1
  36. package/src/hooks/use-recent-posts.ts +8 -47
  37. package/src/hooks/use-rename-active-document.ts +23 -0
  38. package/src/types.ts +12 -0
@@ -3,6 +3,8 @@ import { usePostListContext } from '../../../../contexts/post-list-context';
3
3
  import { usePostActions } from '../../../../hooks/use-posts-actions';
4
4
  import { Post } from '../../../../types';
5
5
  import EditModeTemplate from './edit-mode-template';
6
+ import { __useActiveDocument as useActiveDocument } from '@elementor/editor-documents';
7
+ import useRenameActiveDocument from '../../../../hooks/use-rename-active-document';
6
8
 
7
9
  type Props = {
8
10
  post: Post,
@@ -11,23 +13,35 @@ type Props = {
11
13
  export default function ListItemRename( { post }: Props ) {
12
14
  const { type, resetEditMode } = usePostListContext();
13
15
  const { updatePost } = usePostActions( type );
16
+ const { setError } = usePostListContext();
17
+ const activeDocument = useActiveDocument();
18
+ const rename = useRenameActiveDocument();
14
19
 
15
- const renamePostCallback = ( inputValue: string ) => {
16
- if ( inputValue === post.title.rendered ) {
20
+ const isActive = activeDocument?.id === post.id;
21
+ const title = isActive ? activeDocument?.title : post.title.rendered;
22
+
23
+ const renamePostCallback = async ( inputValue: string ) => {
24
+ if ( inputValue === title ) {
17
25
  resetEditMode();
18
26
  }
19
27
 
20
- updatePost.mutateAsync( {
21
- id: post.id,
22
- title: inputValue,
23
- }, {
24
- onSuccess: () => {
25
- resetEditMode();
26
- },
27
- } );
28
+ try {
29
+ if ( isActive ) {
30
+ await rename( inputValue );
31
+ } else {
32
+ await updatePost.mutateAsync( {
33
+ id: post.id,
34
+ title: inputValue,
35
+ } );
36
+ }
37
+ } catch ( e ) {
38
+ setError();
39
+ } finally {
40
+ resetEditMode();
41
+ }
28
42
  };
29
43
 
30
44
  return (
31
- <EditModeTemplate postTitle={ post.title.rendered } isLoading={ updatePost.isPending } callback={ renamePostCallback } />
45
+ <EditModeTemplate postTitle={ title } isLoading={ updatePost.isPending } callback={ renamePostCallback } />
32
46
  );
33
47
  }
@@ -3,12 +3,11 @@ import {
3
3
  bindMenu,
4
4
  bindTrigger,
5
5
  Divider,
6
+ IconButton,
6
7
  ListItem,
7
8
  ListItemButton,
8
- ListItemIcon,
9
9
  ListItemText,
10
10
  Menu,
11
- ToggleButton,
12
11
  usePopupState,
13
12
  } from '@elementor/ui';
14
13
  import { DotsVerticalIcon, HomeIcon } from '@elementor/icons';
@@ -33,21 +32,21 @@ export default function ListItemView( { post }: { post: Post } ) {
33
32
  } );
34
33
 
35
34
  const isActive = activeDocument?.id === post.id;
35
+ const status = isActive ? activeDocument?.status.value : post.status;
36
+ const title = isActive ? activeDocument?.title : post.title.rendered;
36
37
 
37
38
  return (
38
39
  <>
39
40
  <ListItem
40
41
  disablePadding
41
42
  secondaryAction={
42
- <ToggleButton
43
+ <IconButton
43
44
  value
44
- color="secondary"
45
45
  size="small"
46
- selected={ popupState.isOpen }
47
46
  { ...bindTrigger( popupState ) }
48
47
  >
49
48
  <DotsVerticalIcon fontSize="small" />
50
- </ToggleButton>
49
+ </IconButton>
51
50
  }
52
51
  >
53
52
  <ListItemButton
@@ -58,18 +57,14 @@ export default function ListItemView( { post }: { post: Post } ) {
58
57
  }
59
58
  } }
60
59
  dense
61
- disableGutters
62
60
  >
63
- <ListItemIcon />
64
61
  <ListItemText
65
62
  disableTypography={ true }
66
63
  >
67
- <PageTitleAndStatus page={ post } />
64
+ <PageTitleAndStatus title={ title } status={ status } />
68
65
  </ListItemText>
69
66
  { post.isHome &&
70
- <ListItemIcon>
71
- <HomeIcon titleAccess={ __( 'Homepage', 'elementor' ) } color="disabled" />
72
- </ListItemIcon>
67
+ <HomeIcon titleAccess={ __( 'Homepage', 'elementor' ) } color="disabled" />
73
68
  }
74
69
  </ListItemButton>
75
70
  </ListItem>
@@ -83,7 +78,7 @@ export default function ListItemView( { post }: { post: Post } ) {
83
78
  <Delete post={ post } />
84
79
  <View post={ post } />
85
80
  <Divider />
86
- <SetHome post={ post } />
81
+ <SetHome post={ post } closeMenu={ () => popupState.close() } />
87
82
  </Menu>
88
83
  </>
89
84
  );
@@ -8,6 +8,7 @@ import CollapsibleList from './collapsible-list';
8
8
  import PostListItem from './post-list-item';
9
9
  import { useHomepage } from '../../../hooks/use-homepage';
10
10
  import AddNewButton from '../add-new-button';
11
+ import ErrorState from './error-state';
11
12
 
12
13
  type Props = {
13
14
  isOpenByDefault?: boolean,
@@ -15,9 +16,13 @@ type Props = {
15
16
 
16
17
  export default function PostsCollapsibleList( { isOpenByDefault = false }: Props ) {
17
18
  const { type, editMode } = usePostListContext();
18
- const { data: posts, isLoading: postsLoading } = usePosts( type );
19
+ const { data: posts, isLoading: postsLoading, isError: postsError } = usePosts( type );
19
20
  const { data: homepageSettings } = useHomepage();
20
21
 
22
+ if ( postsError ) {
23
+ return <ErrorState />;
24
+ }
25
+
21
26
  if ( ! posts || postsLoading ) {
22
27
  return (
23
28
  <Box sx={ { px: 5 } }>
@@ -43,12 +48,36 @@ export default function PostsCollapsibleList( { isOpenByDefault = false }: Props
43
48
  const isHomepageSet = homepageSettings?.show_on_front === 'page' && !! homepageSettings?.page_on_front;
44
49
  const homepageId = isHomepageSet ? homepageSettings.page_on_front : null;
45
50
 
51
+ const mappedPosts = posts.map( ( post ) => {
52
+ if ( post.id === homepageId ) {
53
+ return { ...post, isHome: true };
54
+ }
55
+
56
+ return post;
57
+ } );
58
+
59
+ const sortedPosts = mappedPosts.sort( ( a, b ) => {
60
+ if ( a.id === homepageId ) {
61
+ return -1;
62
+ }
63
+
64
+ if ( b.id === homepageId ) {
65
+ return 1;
66
+ }
67
+
68
+ return 0;
69
+ } );
70
+
46
71
  return (
47
72
  <>
48
73
  <Box
49
74
  display="flex"
50
75
  justifyContent="flex-end"
51
76
  alignItems="center"
77
+ sx={ {
78
+ py: 1,
79
+ px: 2,
80
+ } }
52
81
  >
53
82
  <AddNewButton />
54
83
  </Box>
@@ -58,8 +87,7 @@ export default function PostsCollapsibleList( { isOpenByDefault = false }: Props
58
87
  Icon={ PageTypeIcon }
59
88
  isOpenByDefault={ isOpenByDefault || false }
60
89
  >
61
- { posts.map( ( post ) => {
62
- post = { ...post, isHome: post.id === homepageId };
90
+ { sortedPosts.map( ( post ) => {
63
91
  return <PostListItem key={ post.id } post={ post } />;
64
92
  } ) }
65
93
  {
@@ -3,17 +3,21 @@ import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from '@elementor/edit
3
3
  import { __ } from '@wordpress/i18n';
4
4
  import PostsCollapsibleList from './posts-list/posts-collapsible-list';
5
5
  import { PostListContextProvider } from '../../contexts/post-list-context';
6
+ import ErrorSnackbar from './error-snackbar';
6
7
 
7
8
  const Shell = () => {
9
+ const [ isErrorSnackbarOpen, setIsErrorSnackbarOpen ] = React.useState( false );
10
+
8
11
  return (
9
12
  <Panel>
10
13
  <PanelHeader>
11
14
  <PanelHeaderTitle>{ __( 'Pages', 'elementor' ) }</PanelHeaderTitle>
12
15
  </PanelHeader>
13
16
  <PanelBody>
14
- <PostListContextProvider type={ 'page' }>
17
+ <PostListContextProvider type={ 'page' } setError={ () => setIsErrorSnackbarOpen( true ) }>
15
18
  <PostsCollapsibleList isOpenByDefault={ true } />
16
19
  </PostListContextProvider>
20
+ <ErrorSnackbar open={ isErrorSnackbarOpen } onClose={ () => setIsErrorSnackbarOpen( false ) } />
17
21
  </PanelBody>
18
22
  </Panel>
19
23
  );
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
2
  import { Box, Typography } from '@elementor/ui';
3
- import { Post } from '../../types';
4
3
  import { useReverseHtmlEntities } from '../../hooks/use-reverse-html-entities';
5
4
 
6
5
  const PageStatus = ( { status }: { status: string } ) => {
@@ -12,6 +11,7 @@ const PageStatus = ( { status }: { status: string } ) => {
12
11
  <Typography
13
12
  component="span"
14
13
  variant="body2"
14
+ color="text.secondary"
15
15
  sx={ {
16
16
  textTransform: 'capitalize',
17
17
  fontStyle: 'italic',
@@ -31,6 +31,7 @@ const PageTitle = ( { title }: { title: string } ) => {
31
31
  <Typography
32
32
  component="span"
33
33
  variant="body2"
34
+ color="text.secondary"
34
35
  noWrap
35
36
  sx={ {
36
37
  flexBasis: 'auto',
@@ -41,10 +42,10 @@ const PageTitle = ( { title }: { title: string } ) => {
41
42
  );
42
43
  };
43
44
 
44
- export default function PageTitleAndStatus( { page }: { page: Post } ) {
45
+ export default function PageTitleAndStatus( { title, status }: { title: string, status: string } ) {
45
46
  return (
46
47
  <Box display="flex">
47
- <PageTitle title={ page.title.rendered } />&nbsp;<PageStatus status={ page.status } />
48
+ <PageTitle title={ title } />&nbsp;<PageStatus status={ status } />
48
49
  </Box>
49
50
  );
50
51
  }
@@ -3,8 +3,9 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
3
  import { __useActiveDocument as useActiveDocument, __useNavigateToDocument as useNavigateToDocument } from '@elementor/editor-documents';
4
4
  import RecentlyEdited from '../recently-edited';
5
5
  import { createMockDocument } from 'test-utils';
6
- import useRecentPosts, { Post } from '../../../hooks/use-recent-posts';
6
+ import useRecentPosts from '../../../hooks/use-recent-posts';
7
7
  import useCreatePage from '../../../hooks/use-create-page';
8
+ import { RecentPost } from '../../../types';
8
9
 
9
10
  jest.mock( '@elementor/editor-documents', () => ( {
10
11
  __useActiveDocument: jest.fn(),
@@ -30,9 +31,9 @@ describe( '@elementor/recently-edited - Top bar add new page', () => {
30
31
  it( 'should render add new page button', () => {
31
32
  // Arrange.
32
33
  const isLoading = false;
33
- const recentPosts: Post[] = [];
34
+ const recentPosts: RecentPost[] = [];
34
35
 
35
- jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
36
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, data: recentPosts } as ReturnType<typeof useRecentPosts> );
36
37
 
37
38
  render( <RecentlyEdited /> );
38
39
 
@@ -48,11 +49,11 @@ describe( '@elementor/recently-edited - Top bar add new page', () => {
48
49
  it( 'should trigger create page hook on click', async () => {
49
50
  // Arrange.
50
51
  const isLoading = false;
51
- const recentPosts: Post[] = [];
52
+ const recentPosts: RecentPost[] = [];
52
53
  const create = jest.fn().mockReturnValue( Promise.resolve( { id: 123 } ) );
53
54
  const navigateToDocument = jest.fn();
54
55
 
55
- jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
56
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, data: recentPosts } as ReturnType<typeof useRecentPosts> );
56
57
  jest.mocked( useCreatePage ).mockReturnValue( { isLoading, create } );
57
58
  jest.mocked( useNavigateToDocument ).mockReturnValue( navigateToDocument );
58
59
 
@@ -3,7 +3,8 @@ import { fireEvent, render, screen } from '@testing-library/react';
3
3
  import { __useHostDocument as useHostDocument, __useActiveDocument as useActiveDocument, __useNavigateToDocument as useNavigateToDocument } from '@elementor/editor-documents';
4
4
  import RecentlyEdited from '../recently-edited';
5
5
  import { createMockDocument } from 'test-utils';
6
- import useRecentPosts, { Post } from '../../../hooks/use-recent-posts';
6
+ import useRecentPosts from '../../../hooks/use-recent-posts';
7
+ import { RecentPost } from '../../../types';
7
8
 
8
9
  jest.mock( '@elementor/editor-documents', () => ( {
9
10
  __useActiveDocument: jest.fn(),
@@ -13,7 +14,7 @@ jest.mock( '@elementor/editor-documents', () => ( {
13
14
 
14
15
  jest.mock( '../../../hooks/use-recent-posts', () => (
15
16
  {
16
- default: jest.fn( () => ( { isLoading: false, recentPosts: [] } ) ),
17
+ default: jest.fn( () => ( { isLoading: false, data: [] } ) ),
17
18
  __esModule: true,
18
19
  }
19
20
  ) );
@@ -117,9 +118,9 @@ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
117
118
  } ) );
118
119
 
119
120
  const isLoading = false;
120
- const recentPosts: Post[] = [];
121
+ const recentPosts: RecentPost[] = [];
121
122
 
122
- jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
123
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, data: recentPosts } as ReturnType<typeof useRecentPosts> );
123
124
 
124
125
  render( <RecentlyEdited /> );
125
126
 
@@ -144,8 +145,8 @@ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
144
145
  } ) );
145
146
 
146
147
  const isLoading = false;
147
- const recentPosts: Post[] = [ {
148
- id: 1,
148
+ const recentPosts: RecentPost[] = [ {
149
+ id: 2,
149
150
  title: 'Test post',
150
151
  edit_url: 'some_url',
151
152
  type: {
@@ -156,7 +157,7 @@ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
156
157
  date_modified: 123,
157
158
  } ];
158
159
 
159
- jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
160
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, data: recentPosts } as ReturnType<typeof useRecentPosts> );
160
161
 
161
162
  render( <RecentlyEdited /> );
162
163
 
@@ -186,9 +187,20 @@ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
186
187
  } ) );
187
188
 
188
189
  const isLoading = false;
189
- const recentPosts: Post[] = [
190
+ const recentPosts: RecentPost[] = [
190
191
  {
191
192
  id: 1,
193
+ title: 'Header title with special char &#165;',
194
+ edit_url: 'some_url',
195
+ type: {
196
+ post_type: 'post',
197
+ doc_type: 'wp-post',
198
+ label: 'Post',
199
+ },
200
+ date_modified: 123,
201
+ },
202
+ {
203
+ id: 3,
192
204
  title: 'Post title with <h1>HTML</h1>',
193
205
  edit_url: 'some_url',
194
206
  type: {
@@ -211,7 +223,7 @@ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
211
223
  },
212
224
  ];
213
225
 
214
- jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, recentPosts } );
226
+ jest.mocked( useRecentPosts ).mockReturnValue( { isLoading, data: recentPosts } as ReturnType<typeof useRecentPosts> );
215
227
 
216
228
  // Act.
217
229
  render( <RecentlyEdited /> );
@@ -235,7 +247,7 @@ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
235
247
 
236
248
  jest.mocked( useRecentPosts ).mockReturnValue( {
237
249
  isLoading: false,
238
- recentPosts: [ {
250
+ data: [ {
239
251
  id: 123,
240
252
  title: 'Test post',
241
253
  edit_url: 'some_url',
@@ -246,7 +258,7 @@ describe( '@elementor/recently-edited - Top bar Recently Edited', () => {
246
258
  },
247
259
  date_modified: 123,
248
260
  } ],
249
- } );
261
+ } as ReturnType<typeof useRecentPosts> );
250
262
 
251
263
  render( <RecentlyEdited /> );
252
264
 
@@ -1,12 +1,12 @@
1
1
  import DocTypeChip from './chip-doc-type';
2
2
  import { MenuItem, MenuItemProps, ListItemText } from '@elementor/ui';
3
3
  import * as React from 'react';
4
- import { Post } from '../../hooks/use-recent-posts';
5
4
  import { __useNavigateToDocument as useNavigateToDocument } from '@elementor/editor-documents';
6
5
  import { useReverseHtmlEntities } from '../../hooks/use-reverse-html-entities';
6
+ import { RecentPost } from '../../types';
7
7
 
8
8
  type Props = MenuItemProps & {
9
- post: Post;
9
+ post: RecentPost;
10
10
  closePopup: () => void;
11
11
  }
12
12
 
@@ -18,6 +18,7 @@ import { __ } from '@wordpress/i18n';
18
18
  import { PostListItem } from './post-list-item';
19
19
  import { CreatePostListItem } from './create-post-list-item';
20
20
  import { useReverseHtmlEntities } from '../../hooks/use-reverse-html-entities';
21
+ import { NUMBER_OF_RECENT_POSTS } from '../../api/recent-posts';
21
22
 
22
23
  export default function RecentlyEdited() {
23
24
  const activeDocument = useActiveDocument();
@@ -26,7 +27,16 @@ export default function RecentlyEdited() {
26
27
  ? activeDocument
27
28
  : hostDocument;
28
29
 
29
- const { recentPosts } = useRecentPosts( document?.id );
30
+ const { data } = useRecentPosts();
31
+
32
+ const getRecentPosts = () => {
33
+ if ( ! data ) {
34
+ return [];
35
+ }
36
+
37
+ return data.filter( ( post ) => post.id !== document?.id ).splice( 0, NUMBER_OF_RECENT_POSTS - 1 );
38
+ };
39
+ const recentPosts = getRecentPosts();
30
40
 
31
41
  const popupState = usePopupState( {
32
42
  variant: 'popover',
@@ -26,6 +26,7 @@ type ContextType = {
26
26
  editMode: EditMode,
27
27
  setEditMode: Dispatch<SetStateAction<EditMode>>,
28
28
  resetEditMode: () => void,
29
+ setError: () => void,
29
30
  };
30
31
 
31
32
  const defaultValues: ContextType = {
@@ -33,15 +34,18 @@ const defaultValues: ContextType = {
33
34
  editMode: { mode: 'none', details: {} },
34
35
  setEditMode: () => null,
35
36
  resetEditMode: () => null,
37
+ setError: () => null,
36
38
  };
37
39
 
38
40
  export const PostListContext = createContext<ContextType>( defaultValues );
39
41
 
40
42
  export const PostListContextProvider = ( {
41
43
  type,
44
+ setError,
42
45
  children,
43
46
  }: {
44
47
  type: ContextType[ 'type' ],
48
+ setError: ContextType[ 'setError' ],
45
49
  children: ReactNode,
46
50
  } ) => {
47
51
  const [ editMode, setEditMode ] = useState( defaultValues.editMode );
@@ -56,6 +60,7 @@ export const PostListContextProvider = ( {
56
60
  editMode,
57
61
  setEditMode,
58
62
  resetEditMode,
63
+ setError,
59
64
  } }>
60
65
  { children }
61
66
  </PostListContext.Provider>
@@ -1,15 +1,13 @@
1
- import { waitFor, renderHook } from '@testing-library/react';
1
+ import { waitFor } from '@testing-library/react';
2
2
  import apiFetch from '@wordpress/api-fetch';
3
- import useRecentPosts, { endpointPath } from '../use-recent-posts';
3
+ import useRecentPosts from '../use-recent-posts';
4
+ import { renderHookWithQuery } from 'test-utils';
5
+ import { baseUrl } from '../../api/recent-posts';
4
6
 
5
7
  // Mock apiFetch to return a promise that resolves to an empty array.
6
8
  jest.mock( '@wordpress/api-fetch' );
7
9
 
8
10
  describe( 'useRecentPosts', () => {
9
- beforeEach( () => {
10
- jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( [] ) );
11
- } );
12
-
13
11
  afterEach( () => {
14
12
  jest.clearAllMocks();
15
13
  } );
@@ -28,14 +26,15 @@ describe( 'useRecentPosts', () => {
28
26
 
29
27
  jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( posts ) );
30
28
 
31
- const { result } = renderHook( () => useRecentPosts( 1 ) );
29
+ // Act.
30
+ const { component } = renderHookWithQuery( () => useRecentPosts() );
32
31
 
33
32
  expect( apiFetch ).toHaveBeenCalledWith( {
34
- path: endpointPath + '?posts_per_page=5&post__not_in=1',
33
+ path: `${ baseUrl }?posts_per_page=6`,
35
34
  } );
36
35
 
37
36
  await waitFor( () => {
38
- expect( result.current.recentPosts ).toBe( posts );
37
+ expect( component.result.current.data ).toBe( posts );
39
38
  } );
40
39
  } );
41
40
  } );
@@ -1,6 +1,7 @@
1
1
  import { useQueryClient, useMutation } from '@elementor/query';
2
2
  import { createRequest, deleteRequest, updateRequest, duplicateRequest, NewPost, Slug, UpdatePost } from '../api/post';
3
3
  import { postsQueryKey } from './use-posts';
4
+ import { recentPostsQueryKey } from './use-recent-posts';
4
5
 
5
6
  export function usePostActions( postTypeSlug: Slug ) {
6
7
  const invalidatePosts = useInvalidatePosts( postTypeSlug );
@@ -40,7 +41,7 @@ function useInvalidatePosts( postTypeSlug: string ) {
40
41
 
41
42
  return ( options = {} ) => {
42
43
  const queryKey = postsQueryKey( postTypeSlug );
43
-
44
+ queryClient.invalidateQueries( { queryKey: recentPostsQueryKey }, options );
44
45
  return queryClient.invalidateQueries( { queryKey }, options );
45
46
  };
46
47
  }
@@ -1,50 +1,11 @@
1
- import { useEffect, useState } from 'react';
2
- import apiFetch from '@wordpress/api-fetch';
3
- import { addQueryArgs } from '@wordpress/url';
1
+ import { useQuery } from '@elementor/query';
2
+ import { getRequest } from '../api/recent-posts';
4
3
 
5
- export interface Post {
6
- id: number,
7
- title: string,
8
- edit_url: string,
9
- type: {
10
- post_type: string,
11
- doc_type: string,
12
- label: string,
13
- },
14
- date_modified: number,
15
- }
16
-
17
- export const endpointPath = '/elementor/v1/site-navigation/recent-posts';
18
-
19
- export default function useRecentPosts( documentId?: number ) {
20
- const [ recentPosts, setRecentPosts ] = useState<Post[]>( [] );
21
- const [ isLoading, setIsLoading ] = useState( false );
22
-
23
- useEffect( () => {
24
- if ( documentId ) {
25
- setIsLoading( true );
26
-
27
- fetchRecentlyEditedPosts( documentId ).then( ( posts ) => {
28
- setRecentPosts( posts );
29
- setIsLoading( false );
30
- } );
31
- }
32
- }, [ documentId ] );
33
-
34
- return {
35
- isLoading,
36
- recentPosts,
37
- };
38
- }
39
-
40
- async function fetchRecentlyEditedPosts( documentId: number ) {
41
- const queryParams = {
42
- posts_per_page: 5,
43
- post__not_in: documentId,
44
- };
4
+ export const recentPostsQueryKey = [ 'site-navigation', 'recent-posts' ];
45
5
 
46
- return await apiFetch( {
47
- path: addQueryArgs( endpointPath, queryParams ),
48
- } ).then( ( response ) => response as Post[] )
49
- .catch( () => [] );
6
+ export default function useRecentPosts() {
7
+ return useQuery( {
8
+ queryKey: recentPostsQueryKey,
9
+ queryFn: () => getRequest(),
10
+ } );
50
11
  }
@@ -0,0 +1,23 @@
1
+ import { __privateRunCommand as runCommand } from '@elementor/editor-v1-adapters';
2
+ import { ExtendedWindow } from '@elementor/editor-documents';
3
+
4
+ function getV1DocumentsManager() {
5
+ const documentsManager = ( window as unknown as ExtendedWindow ).elementor?.documents;
6
+
7
+ if ( ! documentsManager ) {
8
+ throw new Error( 'Elementor Editor V1 documents manager not found' );
9
+ }
10
+
11
+ return documentsManager;
12
+ }
13
+
14
+ export default function useRenameActiveDocument() {
15
+ return async ( title: string ) => {
16
+ const currentDocument = getV1DocumentsManager().getCurrent();
17
+ const container = currentDocument.container;
18
+ await runCommand( 'document/elements/settings', {
19
+ container,
20
+ settings: { post_title: title },
21
+ } );
22
+ };
23
+ }
package/src/types.ts CHANGED
@@ -8,3 +8,15 @@ export type Post = {
8
8
  rendered: string;
9
9
  }
10
10
  };
11
+
12
+ export type RecentPost = {
13
+ id: number,
14
+ title: string,
15
+ edit_url: string,
16
+ type: {
17
+ post_type: string,
18
+ doc_type: string,
19
+ label: string,
20
+ },
21
+ date_modified: number,
22
+ }