@elementor/editor-site-navigation 0.19.11 → 0.21.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.js +128 -50
  3. package/dist/index.mjs +124 -44
  4. package/package.json +5 -5
  5. package/src/api/post.ts +32 -4
  6. package/src/api/settings.ts +5 -9
  7. package/src/api/user.ts +20 -0
  8. package/src/components/panel/actions-menu/actions/__tests__/delete.test.tsx +8 -0
  9. package/src/components/panel/actions-menu/actions/__tests__/set-home.test.tsx +52 -0
  10. package/src/components/panel/actions-menu/actions/__tests__/view.test.tsx +4 -0
  11. package/src/components/panel/actions-menu/actions/delete.tsx +4 -1
  12. package/src/components/panel/actions-menu/actions/duplicate.tsx +5 -1
  13. package/src/components/panel/actions-menu/actions/rename.tsx +1 -0
  14. package/src/components/panel/actions-menu/actions/set-home.tsx +9 -1
  15. package/src/components/panel/add-new-button.tsx +3 -0
  16. package/src/components/panel/posts-list/__tests__/post-list-item.test.tsx +20 -0
  17. package/src/components/panel/posts-list/__tests__/posts-collapsible-list.test.tsx +56 -8
  18. package/src/components/panel/posts-list/collapsible-list.tsx +1 -1
  19. package/src/components/panel/posts-list/list-items/list-item-view.tsx +50 -29
  20. package/src/components/panel/posts-list/posts-collapsible-list.tsx +14 -7
  21. package/src/components/top-bar/__tests__/add-new-page.test.tsx +36 -0
  22. package/src/components/top-bar/__tests__/recently-edited.test.tsx +68 -0
  23. package/src/components/top-bar/create-post-list-item.tsx +3 -1
  24. package/src/components/top-bar/post-list-item.tsx +1 -0
  25. package/src/hooks/__tests__/use-homepage.test.ts +1 -1
  26. package/src/hooks/__tests__/use-posts.test.ts +44 -10
  27. package/src/hooks/use-posts.ts +25 -4
  28. package/src/hooks/use-user.ts +11 -0
  29. package/src/types.ts +8 -1
package/dist/index.mjs CHANGED
@@ -135,6 +135,7 @@ function PostListItem({ post, closePopup, ...props }) {
135
135
  return /* @__PURE__ */ React3.createElement(
136
136
  MenuItem,
137
137
  {
138
+ disabled: !post.user_can.edit,
138
139
  onClick: async () => {
139
140
  closePopup();
140
141
  await navigateToDocument(post.id);
@@ -190,12 +191,41 @@ async function addNewPage() {
190
191
  import { PlusIcon } from "@elementor/icons";
191
192
  import { __ } from "@wordpress/i18n";
192
193
  import { __useNavigateToDocument as useNavigateToDocument2 } from "@elementor/editor-documents";
194
+
195
+ // src/hooks/use-user.ts
196
+ import { useQuery as useQuery2 } from "@elementor/query";
197
+
198
+ // src/api/user.ts
199
+ import apiFetch3 from "@wordpress/api-fetch";
200
+ var getUser = () => {
201
+ const baseUri = "/wp/v2/users/me";
202
+ const keys = ["capabilities"];
203
+ const queryParams = new URLSearchParams({
204
+ _fields: keys.join(","),
205
+ context: "edit"
206
+ });
207
+ const uri = baseUri + "?" + queryParams.toString();
208
+ return apiFetch3({ path: uri });
209
+ };
210
+
211
+ // src/hooks/use-user.ts
212
+ var userQueryKey = () => ["site-navigation", "user"];
213
+ function useUser() {
214
+ return useQuery2({
215
+ queryKey: userQueryKey(),
216
+ queryFn: () => getUser()
217
+ });
218
+ }
219
+
220
+ // src/components/top-bar/create-post-list-item.tsx
193
221
  function CreatePostListItem({ closePopup, ...props }) {
194
222
  const { create, isLoading } = useCreatePage();
195
223
  const navigateToDocument = useNavigateToDocument2();
224
+ const { data: user } = useUser();
196
225
  return /* @__PURE__ */ React4.createElement(
197
226
  MenuItem2,
198
227
  {
228
+ disabled: isLoading || !user?.capabilities?.edit_pages,
199
229
  onClick: async () => {
200
230
  const { id } = await create();
201
231
  closePopup();
@@ -293,13 +323,13 @@ import { __ as __15 } from "@wordpress/i18n";
293
323
  // src/components/panel/posts-list/posts-collapsible-list.tsx
294
324
  import * as React23 from "react";
295
325
  import { PageTypeIcon as PageTypeIcon2 } from "@elementor/icons";
296
- import { Skeleton, Box as Box4, List as List2 } from "@elementor/ui";
326
+ import { Skeleton, Box as Box4, List as List2, Button as Button4, CircularProgress as CircularProgress5 } from "@elementor/ui";
297
327
 
298
328
  // src/hooks/use-posts.ts
299
- import { useQuery as useQuery2 } from "@elementor/query";
329
+ import { useInfiniteQuery } from "@elementor/query";
300
330
 
301
331
  // src/api/post.ts
302
- import apiFetch3 from "@wordpress/api-fetch";
332
+ import apiFetch4 from "@wordpress/api-fetch";
303
333
  import { __ as __3 } from "@wordpress/i18n";
304
334
  var postTypesMap = {
305
335
  page: {
@@ -310,21 +340,32 @@ var postTypesMap = {
310
340
  rest_base: "pages"
311
341
  }
312
342
  };
313
- var getRequest2 = (postTypeSlug) => {
343
+ var POST_PER_PAGE = 10;
344
+ var getRequest2 = async (postTypeSlug, page) => {
314
345
  const baseUri = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
315
- const keys = ["id", "type", "title", "link", "status"];
346
+ const keys = ["id", "type", "title", "link", "status", "user_can"];
316
347
  const queryParams = new URLSearchParams({
317
348
  status: "any",
318
- per_page: "-1",
319
349
  order: "asc",
350
+ page: page.toString(),
351
+ per_page: POST_PER_PAGE.toString(),
320
352
  _fields: keys.join(",")
321
353
  });
322
354
  const uri = baseUri + "?" + queryParams.toString();
323
- return apiFetch3({ path: uri });
355
+ const result = await apiFetch4({ path: uri, parse: false });
356
+ const data = await result.json();
357
+ const totalPages = Number(result.headers.get("x-wp-totalpages"));
358
+ const totalPosts = Number(result.headers.get("x-wp-total"));
359
+ return {
360
+ data,
361
+ totalPages,
362
+ totalPosts,
363
+ currentPage: page
364
+ };
324
365
  };
325
366
  var createRequest = (postTypeSlug, newPost) => {
326
367
  const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
327
- return apiFetch3({
368
+ return apiFetch4({
328
369
  path,
329
370
  method: "POST",
330
371
  data: newPost
@@ -333,7 +374,7 @@ var createRequest = (postTypeSlug, newPost) => {
333
374
  var updateRequest = (postTypeSlug, updatedPost) => {
334
375
  const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
335
376
  const { id, ...data } = updatedPost;
336
- return apiFetch3({
377
+ return apiFetch4({
337
378
  path: `${path}/${id}`,
338
379
  method: "POST",
339
380
  data
@@ -341,14 +382,14 @@ var updateRequest = (postTypeSlug, updatedPost) => {
341
382
  };
342
383
  var deleteRequest = (postTypeSlug, postId) => {
343
384
  const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
344
- return apiFetch3({
385
+ return apiFetch4({
345
386
  path: `${path}/${postId}`,
346
387
  method: "DELETE"
347
388
  });
348
389
  };
349
390
  var duplicateRequest = (originalPost) => {
350
391
  const path = `/elementor/v1/site-navigation/duplicate-post`;
351
- return apiFetch3({
392
+ return apiFetch4({
352
393
  path,
353
394
  method: "POST",
354
395
  data: {
@@ -360,11 +401,26 @@ var duplicateRequest = (originalPost) => {
360
401
 
361
402
  // src/hooks/use-posts.ts
362
403
  var postsQueryKey = (postTypeSlug) => ["site-navigation", "posts", postTypeSlug];
404
+ var flattenData = (data) => {
405
+ if (!data) {
406
+ return data;
407
+ }
408
+ const flattened = [];
409
+ data.pages.forEach((page) => {
410
+ flattened.push(...page.data);
411
+ });
412
+ return flattened;
413
+ };
363
414
  function usePosts(postTypeSlug) {
364
- return useQuery2({
415
+ const query = useInfiniteQuery({
365
416
  queryKey: postsQueryKey(postTypeSlug),
366
- queryFn: () => getRequest2(postTypeSlug)
417
+ queryFn: ({ pageParam = 1 }) => getRequest2(postTypeSlug, pageParam),
418
+ initialPageParam: 1,
419
+ getNextPageParam: (lastPage) => {
420
+ return lastPage.currentPage < lastPage.totalPages ? lastPage.currentPage + 1 : void 0;
421
+ }
367
422
  });
423
+ return { ...query, data: { posts: flattenData(query.data), total: query.data?.pages[0]?.totalPosts ?? 0 } };
368
424
  }
369
425
 
370
426
  // src/contexts/post-list-context.tsx
@@ -455,7 +511,7 @@ function CollapsibleList({
455
511
  unmountOnExit: true
456
512
  },
457
513
  /* @__PURE__ */ React7.createElement(List, { dense: true }, children)
458
- ), /* @__PURE__ */ React7.createElement(Divider2, { sx: { my: 3 } }));
514
+ ), /* @__PURE__ */ React7.createElement(Divider2, { sx: { mt: 1 } }));
459
515
  }
460
516
 
461
517
  // src/components/panel/posts-list/post-list-item.tsx
@@ -705,6 +761,8 @@ import {
705
761
  ListItemButton,
706
762
  ListItemText as ListItemText7,
707
763
  Menu as Menu2,
764
+ Tooltip as Tooltip2,
765
+ Typography as Typography3,
708
766
  usePopupState as usePopupState2
709
767
  } from "@elementor/ui";
710
768
  import { DotsVerticalIcon, HomeIcon as HomeIcon2 } from "@elementor/icons";
@@ -782,6 +840,7 @@ function Rename({ post }) {
782
840
  title: __7("Rename", "elementor"),
783
841
  icon: EraseIcon,
784
842
  MenuItemProps: {
843
+ disabled: !post.user_can.edit,
785
844
  onClick: () => {
786
845
  setEditMode({
787
846
  mode: "rename",
@@ -801,6 +860,7 @@ import { CopyIcon } from "@elementor/icons";
801
860
  import { __ as __8 } from "@wordpress/i18n";
802
861
  function Duplicate({ post, popupState }) {
803
862
  const { setEditMode } = usePostListContext();
863
+ const { data: user } = useUser();
804
864
  const onClick = () => {
805
865
  popupState.close();
806
866
  setEditMode({
@@ -811,12 +871,14 @@ function Duplicate({ post, popupState }) {
811
871
  }
812
872
  });
813
873
  };
874
+ const isDisabled = !user?.capabilities?.edit_pages;
814
875
  return /* @__PURE__ */ React15.createElement(
815
876
  ActionMenuItem,
816
877
  {
817
878
  title: __8("Duplicate", "elementor"),
818
879
  icon: CopyIcon,
819
880
  MenuItemProps: {
881
+ disabled: isDisabled,
820
882
  onClick
821
883
  }
822
884
  }
@@ -843,13 +905,15 @@ function Delete({ post }) {
843
905
  const [isDialogOpen, setIsDialogOpen] = useState5(false);
844
906
  const activeDocument = useActiveDocument3();
845
907
  const isPostActive = activeDocument?.id === post.id;
908
+ const userCanDelete = post.user_can.delete;
909
+ const isDisabled = !userCanDelete || post.isHome || isPostActive;
846
910
  return /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(
847
911
  ActionMenuItem,
848
912
  {
849
913
  title: __9("Delete", "elementor"),
850
914
  icon: TrashIcon,
851
915
  MenuItemProps: {
852
- disabled: post.isHome || isPostActive,
916
+ disabled: isDisabled,
853
917
  onClick: () => setIsDialogOpen(true),
854
918
  sx: { "&:hover": { color: "error.main" } }
855
919
  }
@@ -917,18 +981,14 @@ import { __ as __11 } from "@wordpress/i18n";
917
981
  import { useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@elementor/query";
918
982
 
919
983
  // src/api/settings.ts
920
- import apiFetch4 from "@wordpress/api-fetch";
984
+ import apiFetch5 from "@wordpress/api-fetch";
921
985
  var getSettings = () => {
922
- const baseUri = "/wp/v2/settings";
923
- const keys = ["show_on_front", "page_on_front"];
924
- const queryParams = new URLSearchParams({
925
- _fields: keys.join(",")
926
- });
927
- const uri = baseUri + "?" + queryParams.toString();
928
- return apiFetch4({ path: uri });
986
+ const baseUri = "/elementor/v1/site-navigation/homepage";
987
+ const uri = baseUri;
988
+ return apiFetch5({ path: uri });
929
989
  };
930
990
  var updateSettings = (settings) => {
931
- return apiFetch4({
991
+ return apiFetch5({
932
992
  path: "/wp/v2/settings",
933
993
  method: "POST",
934
994
  data: settings
@@ -968,6 +1028,7 @@ import { CircularProgress as CircularProgress4 } from "@elementor/ui";
968
1028
  function SetHome({ post, closeMenu }) {
969
1029
  const { updateSettingsMutation } = useHomepageActions();
970
1030
  const { setError } = usePostListContext();
1031
+ const { data: user } = useUser();
971
1032
  const handleClick = async () => {
972
1033
  try {
973
1034
  await updateSettingsMutation.mutateAsync({ show_on_front: "page", page_on_front: post.id });
@@ -977,13 +1038,17 @@ function SetHome({ post, closeMenu }) {
977
1038
  closeMenu();
978
1039
  }
979
1040
  };
1041
+ const canManageOptions = !!user?.capabilities?.manage_options;
1042
+ const isPostPublished = post.status === "publish";
1043
+ const isPostHomepage = !!post.isHome;
1044
+ const isDisabled = !canManageOptions || isPostHomepage || !isPostPublished || updateSettingsMutation.isPending;
980
1045
  return /* @__PURE__ */ React18.createElement(
981
1046
  ActionMenuItem,
982
1047
  {
983
1048
  title: __11("Set as homepage", "elementor"),
984
1049
  icon: !updateSettingsMutation.isPending ? HomeIcon : CircularProgress4,
985
1050
  MenuItemProps: {
986
- disabled: !!post.isHome || post.status !== "publish" || updateSettingsMutation.isPending,
1051
+ disabled: isDisabled,
987
1052
  onClick: handleClick
988
1053
  }
989
1054
  }
@@ -992,6 +1057,21 @@ function SetHome({ post, closeMenu }) {
992
1057
 
993
1058
  // src/components/panel/posts-list/list-items/list-item-view.tsx
994
1059
  import { __ as __12 } from "@wordpress/i18n";
1060
+ var DisabledPostTooltip = ({ children, isDisabled }) => {
1061
+ if (isDisabled) {
1062
+ const title = /* @__PURE__ */ React19.createElement(Typography3, { variant: "caption" }, "You cannot edit this page.", /* @__PURE__ */ React19.createElement("br", null), "To edit it directly, contact the site owner");
1063
+ return /* @__PURE__ */ React19.createElement(
1064
+ Tooltip2,
1065
+ {
1066
+ title,
1067
+ placement: "bottom",
1068
+ arrow: false
1069
+ },
1070
+ children
1071
+ );
1072
+ }
1073
+ return /* @__PURE__ */ React19.createElement(React19.Fragment, null, children);
1074
+ };
995
1075
  function ListItemView({ post }) {
996
1076
  const activeDocument = useActiveDocument4();
997
1077
  const navigateToDocument = useNavigateToDocument5();
@@ -1003,7 +1083,8 @@ function ListItemView({ post }) {
1003
1083
  const isActive = activeDocument?.id === post.id;
1004
1084
  const status = isActive ? activeDocument?.status.value : post.status;
1005
1085
  const title = isActive ? activeDocument?.title : post.title.rendered;
1006
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(
1086
+ const isDisabled = !post.user_can.edit;
1087
+ return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(DisabledPostTooltip, { isDisabled }, /* @__PURE__ */ React19.createElement(
1007
1088
  ListItem3,
1008
1089
  {
1009
1090
  disablePadding: true,
@@ -1021,6 +1102,7 @@ function ListItemView({ post }) {
1021
1102
  ListItemButton,
1022
1103
  {
1023
1104
  selected: isActive,
1105
+ disabled: isDisabled,
1024
1106
  onClick: () => {
1025
1107
  if (!isActive) {
1026
1108
  navigateToDocument(post.id);
@@ -1028,16 +1110,10 @@ function ListItemView({ post }) {
1028
1110
  },
1029
1111
  dense: true
1030
1112
  },
1031
- /* @__PURE__ */ React19.createElement(
1032
- ListItemText7,
1033
- {
1034
- disableTypography: true
1035
- },
1036
- /* @__PURE__ */ React19.createElement(PageTitleAndStatus, { title, status })
1037
- ),
1113
+ /* @__PURE__ */ React19.createElement(ListItemText7, { disableTypography: true }, /* @__PURE__ */ React19.createElement(PageTitleAndStatus, { title, status })),
1038
1114
  post.isHome && /* @__PURE__ */ React19.createElement(HomeIcon2, { titleAccess: __12("Homepage", "elementor"), color: "disabled" })
1039
1115
  )
1040
- ), /* @__PURE__ */ React19.createElement(
1116
+ )), /* @__PURE__ */ React19.createElement(
1041
1117
  Menu2,
1042
1118
  {
1043
1119
  PaperProps: { sx: { mt: 2, width: 200 } },
@@ -1078,11 +1154,13 @@ import { PlusIcon as PlusIcon2 } from "@elementor/icons";
1078
1154
  import { __ as __13 } from "@wordpress/i18n";
1079
1155
  function AddNewButton() {
1080
1156
  const { setEditMode } = usePostListContext();
1157
+ const { data: user } = useUser();
1081
1158
  return /* @__PURE__ */ React21.createElement(
1082
1159
  Button3,
1083
1160
  {
1084
1161
  size: "small",
1085
1162
  startIcon: /* @__PURE__ */ React21.createElement(PlusIcon2, null),
1163
+ disabled: !user?.capabilities?.edit_pages,
1086
1164
  onClick: () => {
1087
1165
  setEditMode({ mode: "create", details: {} });
1088
1166
  },
@@ -1096,7 +1174,7 @@ function AddNewButton() {
1096
1174
 
1097
1175
  // src/components/panel/posts-list/error-state.tsx
1098
1176
  import { Error404TemplateIcon } from "@elementor/icons";
1099
- import { Box as Box3, Link, Typography as Typography3 } from "@elementor/ui";
1177
+ import { Box as Box3, Link, Typography as Typography4 } from "@elementor/ui";
1100
1178
  import { __ as __14 } from "@wordpress/i18n";
1101
1179
  import * as React22 from "react";
1102
1180
  function ErrorState() {
@@ -1113,14 +1191,14 @@ function ErrorState() {
1113
1191
  justifyContent: "center",
1114
1192
  alignItems: "center",
1115
1193
  gap: "8px"
1116
- } }, /* @__PURE__ */ React22.createElement(Typography3, { variant: "body1", color: "text.primary" }, __14("We couldn\u2019t display your pages.", "elementor")), /* @__PURE__ */ React22.createElement(Box3, null, /* @__PURE__ */ React22.createElement(Typography3, { variant: "body2", color: "text.primary", sx: { textAlign: "center" } }, __14("It\u2019s probably a temporary issue.", "elementor")), /* @__PURE__ */ React22.createElement(Typography3, { variant: "body2", color: "text.primary", sx: { textAlign: "center" } }, __14("If the problem persists,", "elementor"), " ", /* @__PURE__ */ React22.createElement(Link, { target: "_blank", href: "https://go.elementor.com/wp-editor-support-open-ticket/" }, "Notify support")))));
1194
+ } }, /* @__PURE__ */ React22.createElement(Typography4, { variant: "body1", color: "text.primary" }, __14("We couldn\u2019t display your pages.", "elementor")), /* @__PURE__ */ React22.createElement(Box3, null, /* @__PURE__ */ React22.createElement(Typography4, { variant: "body2", color: "text.primary", sx: { textAlign: "center" } }, __14("It\u2019s probably a temporary issue.", "elementor")), /* @__PURE__ */ React22.createElement(Typography4, { variant: "body2", color: "text.primary", sx: { textAlign: "center" } }, __14("If the problem persists,", "elementor"), " ", /* @__PURE__ */ React22.createElement(Link, { target: "_blank", href: "https://go.elementor.com/wp-editor-support-open-ticket/" }, "Notify support")))));
1117
1195
  }
1118
1196
 
1119
1197
  // src/components/panel/posts-list/posts-collapsible-list.tsx
1120
1198
  function PostsCollapsibleList({ isOpenByDefault = false }) {
1121
1199
  const { type, editMode } = usePostListContext();
1122
- const { data: posts, isLoading: postsLoading, isError: postsError } = usePosts(type);
1123
- const { data: homepageSettings } = useHomepage();
1200
+ const { data: { posts, total }, isLoading: postsLoading, isError: postsError, fetchNextPage, hasNextPage, isFetchingNextPage } = usePosts(type);
1201
+ const { data: homepageId } = useHomepage();
1124
1202
  if (postsError) {
1125
1203
  return /* @__PURE__ */ React23.createElement(ErrorState, null);
1126
1204
  }
@@ -1135,9 +1213,7 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
1135
1213
  /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 4 }, animation: "wave", variant: "rounded", width: "110px", height: "28px" })
1136
1214
  ), /* @__PURE__ */ React23.createElement(Box4, null, /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "100%", height: "24px" }), /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" })));
1137
1215
  }
1138
- const label = `${postTypesMap[type].labels.plural_name} (${posts.length.toString()})`;
1139
- const isHomepageSet = homepageSettings?.show_on_front === "page" && !!homepageSettings?.page_on_front;
1140
- const homepageId = isHomepageSet ? homepageSettings.page_on_front : null;
1216
+ const label = `${postTypesMap[type].labels.plural_name} (${total.toString()})`;
1141
1217
  const mappedPosts = posts.map((post) => {
1142
1218
  if (post.id === homepageId) {
1143
1219
  return { ...post, isHome: true };
@@ -1175,12 +1251,16 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
1175
1251
  sortedPosts.map((post) => {
1176
1252
  return /* @__PURE__ */ React23.createElement(PostListItem2, { key: post.id, post });
1177
1253
  }),
1178
- ["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React23.createElement(PostListItem2, null)
1254
+ ["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React23.createElement(PostListItem2, null),
1255
+ hasNextPage && /* @__PURE__ */ React23.createElement(Box4, { sx: {
1256
+ display: "flex",
1257
+ justifyContent: "center"
1258
+ } }, /* @__PURE__ */ React23.createElement(Button4, { onClick: fetchNextPage, color: "secondary" }, isFetchingNextPage ? /* @__PURE__ */ React23.createElement(CircularProgress5, null) : "Load More"))
1179
1259
  )));
1180
1260
  }
1181
1261
 
1182
1262
  // src/components/panel/error-snackbar.tsx
1183
- import { Snackbar, Alert, Typography as Typography4 } from "@elementor/ui";
1263
+ import { Snackbar, Alert, Typography as Typography5 } from "@elementor/ui";
1184
1264
  import * as React24 from "react";
1185
1265
  var ErrorSnackbar = ({ open, onClose }) => {
1186
1266
  return /* @__PURE__ */ React24.createElement(
@@ -1194,7 +1274,7 @@ var ErrorSnackbar = ({ open, onClose }) => {
1194
1274
  }
1195
1275
  },
1196
1276
  /* @__PURE__ */ React24.createElement(Alert, { onClose, severity: "error", sx: { width: "100%" } }, /* @__PURE__ */ React24.createElement(
1197
- Typography4,
1277
+ Typography5,
1198
1278
  {
1199
1279
  component: "span",
1200
1280
  sx: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-site-navigation",
3
- "version": "0.19.11",
3
+ "version": "0.21.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -32,13 +32,13 @@
32
32
  "dev": "tsup --config=../../tsup.dev.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@elementor/editor-app-bar": "^0.9.7",
35
+ "@elementor/editor-app-bar": "^0.9.8",
36
36
  "@elementor/editor-documents": "^0.10.1",
37
- "@elementor/editor-panels": "^0.4.6",
37
+ "@elementor/editor-panels": "^0.4.7",
38
38
  "@elementor/editor-v1-adapters": "^0.6.0",
39
39
  "@elementor/env": "^0.3.2",
40
40
  "@elementor/icons": "^0.7.2",
41
- "@elementor/query": "^0.1.6",
41
+ "@elementor/query": "^0.2.0",
42
42
  "@elementor/ui": "^1.4.61",
43
43
  "@wordpress/api-fetch": "^6.42.0",
44
44
  "@wordpress/i18n": "^4.45.0",
@@ -50,5 +50,5 @@
50
50
  "elementor": {
51
51
  "type": "extension"
52
52
  },
53
- "gitHead": "82b8732700123fe0b916a2e7e78d91a55f5bb7e1"
53
+ "gitHead": "780188b3bf19f2d02edabce30374f89b1fecbc07"
54
54
  }
package/src/api/post.ts CHANGED
@@ -24,21 +24,49 @@ export const postTypesMap = {
24
24
  },
25
25
  };
26
26
 
27
- export const getRequest = ( postTypeSlug: Slug ) => {
27
+ export const POST_PER_PAGE = 10;
28
+
29
+ type WpPostsResponse = {
30
+ json: () => Promise<Post[]>,
31
+ headers: {
32
+ get: ( key: string ) => string | null,
33
+ },
34
+ }
35
+
36
+ export type PostsResponse = {
37
+ data: Post[],
38
+ totalPages: number,
39
+ totalPosts: number,
40
+ currentPage: number,
41
+ };
42
+
43
+ export const getRequest = async ( postTypeSlug: Slug, page: number ): Promise<PostsResponse> => {
28
44
  const baseUri = `/wp/v2/${ postTypesMap[ postTypeSlug ].rest_base }`;
29
45
 
30
- const keys: Array<keyof Post> = [ 'id', 'type', 'title', 'link', 'status' ];
46
+ const keys: Array<keyof Post> = [ 'id', 'type', 'title', 'link', 'status', 'user_can' ];
31
47
 
32
48
  const queryParams = new URLSearchParams( {
33
49
  status: 'any',
34
- per_page: '-1',
35
50
  order: 'asc',
51
+ page: page.toString(),
52
+ per_page: POST_PER_PAGE.toString(),
36
53
  _fields: keys.join( ',' ),
37
54
  } );
38
55
 
39
56
  const uri = baseUri + '?' + queryParams.toString();
40
57
 
41
- return apiFetch<Post[]>( { path: uri } );
58
+ const result = await apiFetch<WpPostsResponse>( { path: uri, parse: false } );
59
+ const data = await result.json();
60
+
61
+ const totalPages = Number( result.headers.get( 'x-wp-totalpages' ) );
62
+ const totalPosts = Number( result.headers.get( 'x-wp-total' ) );
63
+
64
+ return {
65
+ data,
66
+ totalPages,
67
+ totalPosts,
68
+ currentPage: page,
69
+ };
42
70
  };
43
71
 
44
72
  export const createRequest = ( postTypeSlug: Slug, newPost: NewPost ) => {
@@ -5,18 +5,14 @@ export type Settings = {
5
5
  page_on_front: number,
6
6
  }
7
7
 
8
- export const getSettings = () => {
9
- const baseUri = '/wp/v2/settings';
10
-
11
- const keys: Array<keyof Settings> = [ 'show_on_front', 'page_on_front' ];
8
+ type Homepage = number;
12
9
 
13
- const queryParams = new URLSearchParams( {
14
- _fields: keys.join( ',' ),
15
- } );
10
+ export const getSettings = () => {
11
+ const baseUri = '/elementor/v1/site-navigation/homepage';
16
12
 
17
- const uri = baseUri + '?' + queryParams.toString();
13
+ const uri = baseUri;
18
14
 
19
- return apiFetch<Settings>( { path: uri } );
15
+ return apiFetch<Homepage>( { path: uri } );
20
16
  };
21
17
 
22
18
  export const updateSettings = ( settings: Settings ) => {
@@ -0,0 +1,20 @@
1
+ import apiFetch from '@wordpress/api-fetch';
2
+
3
+ export type User = {
4
+ capabilities: Record<string, boolean>,
5
+ }
6
+
7
+ export const getUser = () => {
8
+ const baseUri = '/wp/v2/users/me';
9
+
10
+ const keys: Array<keyof User> = [ 'capabilities' ];
11
+
12
+ const queryParams = new URLSearchParams( {
13
+ _fields: keys.join( ',' ),
14
+ context: 'edit',
15
+ } );
16
+
17
+ const uri = baseUri + '?' + queryParams.toString();
18
+
19
+ return apiFetch<User>( { path: uri } );
20
+ };
@@ -33,6 +33,10 @@ describe( '@elementor/editor-site-navigation/pages-panel-actions - Delete', () =
33
33
  type: 'page',
34
34
  link: 'https://example.local/test-page',
35
35
  isHome: false,
36
+ user_can: {
37
+ edit: true,
38
+ delete: true,
39
+ },
36
40
  };
37
41
 
38
42
  // Act.
@@ -70,6 +74,10 @@ describe( '@elementor/editor-site-navigation/pages-panel-actions - Delete', () =
70
74
  type: 'page',
71
75
  link: 'https://example.local/test-page',
72
76
  isHome: true,
77
+ user_can: {
78
+ edit: true,
79
+ delete: true,
80
+ },
73
81
  };
74
82
 
75
83
  // Act.
@@ -2,6 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
2
2
  import SetHome from '../set-home';
3
3
  import * as React from 'react';
4
4
  import { Post } from '../../../../../types';
5
+ import useUser from '../../../../../hooks/use-user';
5
6
 
6
7
  const mockMutateAsync = jest.fn();
7
8
  jest.mock( '../../../../../hooks/use-homepage-actions', () => ( {
@@ -14,6 +15,15 @@ jest.mock( '../../../../../hooks/use-homepage-actions', () => ( {
14
15
  } ) ),
15
16
  } ) );
16
17
 
18
+ jest.mock( '../../../../../hooks/use-user', () => (
19
+ {
20
+ default: jest.fn( () => ( { isLoading: false, data: { capabilities: {
21
+ manage_options: true,
22
+ } } } ) ),
23
+ __esModule: true,
24
+ }
25
+ ) );
26
+
17
27
  describe( '@elementor/editor-site-navigation - SetHome', () => {
18
28
  afterAll( () => {
19
29
  jest.clearAllMocks();
@@ -30,6 +40,10 @@ describe( '@elementor/editor-site-navigation - SetHome', () => {
30
40
  type: 'page',
31
41
  link: 'https://example.local/test-page',
32
42
  isHome: false,
43
+ user_can: {
44
+ edit: true,
45
+ delete: true,
46
+ },
33
47
  };
34
48
 
35
49
  // Act.
@@ -55,6 +69,44 @@ describe( '@elementor/editor-site-navigation - SetHome', () => {
55
69
  type: 'page',
56
70
  link: 'https://example.local/test-page',
57
71
  isHome: false,
72
+ user_can: {
73
+ edit: true,
74
+ delete: true,
75
+ },
76
+ };
77
+
78
+ // Act.
79
+ render( <SetHome post={ post } closeMenu={ () => {} } /> );
80
+
81
+ // Assert.
82
+ const button = screen.getByRole( 'menuitem' );
83
+ expect( button ).toHaveAttribute( 'aria-disabled', 'true' );
84
+ } );
85
+
86
+ it( 'should render Set as homepage disabled when the user cant manage options', () => {
87
+ // Arrange.
88
+ jest.mocked( useUser ).mockReturnValue( {
89
+ isLoading: false,
90
+ data: {
91
+ capabilities: {
92
+ manage_options: false,
93
+ },
94
+ },
95
+ } as unknown as ReturnType<typeof useUser> );
96
+
97
+ const post: Post = {
98
+ id: 1,
99
+ title: {
100
+ rendered: 'Test Page',
101
+ },
102
+ status: 'publish',
103
+ type: 'page',
104
+ link: 'https://example.local/test-page',
105
+ isHome: false,
106
+ user_can: {
107
+ edit: true,
108
+ delete: true,
109
+ },
58
110
  };
59
111
 
60
112
  // Act.
@@ -18,6 +18,10 @@ describe( '@elementor/editor-site-navigation - View', () => {
18
18
  link: 'mocklink',
19
19
  status: 'publish',
20
20
  type: 'page',
21
+ user_can: {
22
+ edit: true,
23
+ delete: true,
24
+ },
21
25
  };
22
26
 
23
27
  // Act.
@@ -23,6 +23,9 @@ export default function Delete( { post }: { post: Post } ) {
23
23
  const activeDocument = useActiveDocument();
24
24
 
25
25
  const isPostActive = activeDocument?.id === post.id;
26
+ const userCanDelete = post.user_can.delete;
27
+
28
+ const isDisabled = ( ! userCanDelete ) || post.isHome || isPostActive;
26
29
 
27
30
  return (
28
31
  <>
@@ -31,7 +34,7 @@ export default function Delete( { post }: { post: Post } ) {
31
34
  icon={ TrashIcon }
32
35
  MenuItemProps={
33
36
  {
34
- disabled: post.isHome || isPostActive,
37
+ disabled: isDisabled,
35
38
  onClick: () => setIsDialogOpen( true ),
36
39
  sx: { '&:hover': { color: 'error.main' } },
37
40
  }