@elementor/editor-site-navigation 0.19.11 → 0.20.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 (27) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/index.js +91 -43
  3. package/dist/index.mjs +88 -38
  4. package/package.json +2 -2
  5. package/src/api/post.ts +1 -1
  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 +12 -6
  18. package/src/components/panel/posts-list/list-items/list-item-view.tsx +50 -29
  19. package/src/components/panel/posts-list/posts-collapsible-list.tsx +1 -4
  20. package/src/components/top-bar/__tests__/add-new-page.test.tsx +36 -0
  21. package/src/components/top-bar/__tests__/recently-edited.test.tsx +68 -0
  22. package/src/components/top-bar/create-post-list-item.tsx +3 -1
  23. package/src/components/top-bar/post-list-item.tsx +1 -0
  24. package/src/hooks/__tests__/use-homepage.test.ts +1 -1
  25. package/src/hooks/__tests__/use-posts.test.ts +1 -1
  26. package/src/hooks/use-user.ts +11 -0
  27. package/src/types.ts +8 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [0.20.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.19.11...@elementor/editor-site-navigation@0.20.0) (2024-01-07)
7
+
8
+
9
+ ### Features
10
+
11
+ * **editor-site-navigation:** add permission based UI disabling [ED-13216] ([#152](https://github.com/elementor/elementor-packages/issues/152)) ([45f3e99](https://github.com/elementor/elementor-packages/commit/45f3e9955149045487ce21db9006c703e7777006))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [0.19.11](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.19.10...@elementor/editor-site-navigation@0.19.11) (2024-01-04)
7
18
 
8
19
 
package/dist/index.js CHANGED
@@ -153,6 +153,7 @@ function PostListItem({ post, closePopup, ...props }) {
153
153
  return /* @__PURE__ */ React3.createElement(
154
154
  import_ui3.MenuItem,
155
155
  {
156
+ disabled: !post.user_can.edit,
156
157
  onClick: async () => {
157
158
  closePopup();
158
159
  await navigateToDocument(post.id);
@@ -208,12 +209,41 @@ async function addNewPage() {
208
209
  var import_icons3 = require("@elementor/icons");
209
210
  var import_i18n = require("@wordpress/i18n");
210
211
  var import_editor_documents2 = require("@elementor/editor-documents");
212
+
213
+ // src/hooks/use-user.ts
214
+ var import_query2 = require("@elementor/query");
215
+
216
+ // src/api/user.ts
217
+ var import_api_fetch3 = __toESM(require("@wordpress/api-fetch"));
218
+ var getUser = () => {
219
+ const baseUri = "/wp/v2/users/me";
220
+ const keys = ["capabilities"];
221
+ const queryParams = new URLSearchParams({
222
+ _fields: keys.join(","),
223
+ context: "edit"
224
+ });
225
+ const uri = baseUri + "?" + queryParams.toString();
226
+ return (0, import_api_fetch3.default)({ path: uri });
227
+ };
228
+
229
+ // src/hooks/use-user.ts
230
+ var userQueryKey = () => ["site-navigation", "user"];
231
+ function useUser() {
232
+ return (0, import_query2.useQuery)({
233
+ queryKey: userQueryKey(),
234
+ queryFn: () => getUser()
235
+ });
236
+ }
237
+
238
+ // src/components/top-bar/create-post-list-item.tsx
211
239
  function CreatePostListItem({ closePopup, ...props }) {
212
240
  const { create, isLoading } = useCreatePage();
213
241
  const navigateToDocument = (0, import_editor_documents2.__useNavigateToDocument)();
242
+ const { data: user } = useUser();
214
243
  return /* @__PURE__ */ React4.createElement(
215
244
  import_ui4.MenuItem,
216
245
  {
246
+ disabled: isLoading || !user?.capabilities?.edit_pages,
217
247
  onClick: async () => {
218
248
  const { id } = await create();
219
249
  closePopup();
@@ -314,10 +344,10 @@ var import_icons15 = require("@elementor/icons");
314
344
  var import_ui15 = require("@elementor/ui");
315
345
 
316
346
  // src/hooks/use-posts.ts
317
- var import_query2 = require("@elementor/query");
347
+ var import_query3 = require("@elementor/query");
318
348
 
319
349
  // src/api/post.ts
320
- var import_api_fetch3 = __toESM(require("@wordpress/api-fetch"));
350
+ var import_api_fetch4 = __toESM(require("@wordpress/api-fetch"));
321
351
  var import_i18n3 = require("@wordpress/i18n");
322
352
  var postTypesMap = {
323
353
  page: {
@@ -330,7 +360,7 @@ var postTypesMap = {
330
360
  };
331
361
  var getRequest2 = (postTypeSlug) => {
332
362
  const baseUri = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
333
- const keys = ["id", "type", "title", "link", "status"];
363
+ const keys = ["id", "type", "title", "link", "status", "user_can"];
334
364
  const queryParams = new URLSearchParams({
335
365
  status: "any",
336
366
  per_page: "-1",
@@ -338,11 +368,11 @@ var getRequest2 = (postTypeSlug) => {
338
368
  _fields: keys.join(",")
339
369
  });
340
370
  const uri = baseUri + "?" + queryParams.toString();
341
- return (0, import_api_fetch3.default)({ path: uri });
371
+ return (0, import_api_fetch4.default)({ path: uri });
342
372
  };
343
373
  var createRequest = (postTypeSlug, newPost) => {
344
374
  const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
345
- return (0, import_api_fetch3.default)({
375
+ return (0, import_api_fetch4.default)({
346
376
  path,
347
377
  method: "POST",
348
378
  data: newPost
@@ -351,7 +381,7 @@ var createRequest = (postTypeSlug, newPost) => {
351
381
  var updateRequest = (postTypeSlug, updatedPost) => {
352
382
  const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
353
383
  const { id, ...data } = updatedPost;
354
- return (0, import_api_fetch3.default)({
384
+ return (0, import_api_fetch4.default)({
355
385
  path: `${path}/${id}`,
356
386
  method: "POST",
357
387
  data
@@ -359,14 +389,14 @@ var updateRequest = (postTypeSlug, updatedPost) => {
359
389
  };
360
390
  var deleteRequest = (postTypeSlug, postId) => {
361
391
  const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
362
- return (0, import_api_fetch3.default)({
392
+ return (0, import_api_fetch4.default)({
363
393
  path: `${path}/${postId}`,
364
394
  method: "DELETE"
365
395
  });
366
396
  };
367
397
  var duplicateRequest = (originalPost) => {
368
398
  const path = `/elementor/v1/site-navigation/duplicate-post`;
369
- return (0, import_api_fetch3.default)({
399
+ return (0, import_api_fetch4.default)({
370
400
  path,
371
401
  method: "POST",
372
402
  data: {
@@ -379,7 +409,7 @@ var duplicateRequest = (originalPost) => {
379
409
  // src/hooks/use-posts.ts
380
410
  var postsQueryKey = (postTypeSlug) => ["site-navigation", "posts", postTypeSlug];
381
411
  function usePosts(postTypeSlug) {
382
- return (0, import_query2.useQuery)({
412
+ return (0, import_query3.useQuery)({
383
413
  queryKey: postsQueryKey(postTypeSlug),
384
414
  queryFn: () => getRequest2(postTypeSlug)
385
415
  });
@@ -483,23 +513,23 @@ var React20 = __toESM(require("react"));
483
513
  var React9 = __toESM(require("react"));
484
514
 
485
515
  // src/hooks/use-posts-actions.ts
486
- var import_query3 = require("@elementor/query");
516
+ var import_query4 = require("@elementor/query");
487
517
  function usePostActions(postTypeSlug) {
488
518
  const invalidatePosts = useInvalidatePosts(postTypeSlug);
489
519
  const onSuccess = () => invalidatePosts({ exact: true });
490
- const createPost = (0, import_query3.useMutation)({
520
+ const createPost = (0, import_query4.useMutation)({
491
521
  mutationFn: (newPost) => createRequest(postTypeSlug, newPost),
492
522
  onSuccess
493
523
  });
494
- const updatePost = (0, import_query3.useMutation)({
524
+ const updatePost = (0, import_query4.useMutation)({
495
525
  mutationFn: (updatedPost) => updateRequest(postTypeSlug, updatedPost),
496
526
  onSuccess
497
527
  });
498
- const deletePost = (0, import_query3.useMutation)({
528
+ const deletePost = (0, import_query4.useMutation)({
499
529
  mutationFn: (postId) => deleteRequest(postTypeSlug, postId),
500
530
  onSuccess
501
531
  });
502
- const duplicatePost = (0, import_query3.useMutation)({
532
+ const duplicatePost = (0, import_query4.useMutation)({
503
533
  mutationFn: (originalPost) => duplicateRequest(originalPost),
504
534
  onSuccess
505
535
  });
@@ -511,7 +541,7 @@ function usePostActions(postTypeSlug) {
511
541
  };
512
542
  }
513
543
  function useInvalidatePosts(postTypeSlug) {
514
- const queryClient = (0, import_query3.useQueryClient)();
544
+ const queryClient = (0, import_query4.useQueryClient)();
515
545
  return (options = {}) => {
516
546
  const queryKey = postsQueryKey(postTypeSlug);
517
547
  queryClient.invalidateQueries({ queryKey: recentPostsQueryKey }, options);
@@ -779,6 +809,7 @@ function Rename({ post }) {
779
809
  title: (0, import_i18n7.__)("Rename", "elementor"),
780
810
  icon: import_icons7.EraseIcon,
781
811
  MenuItemProps: {
812
+ disabled: !post.user_can.edit,
782
813
  onClick: () => {
783
814
  setEditMode({
784
815
  mode: "rename",
@@ -798,6 +829,7 @@ var import_icons8 = require("@elementor/icons");
798
829
  var import_i18n8 = require("@wordpress/i18n");
799
830
  function Duplicate({ post, popupState }) {
800
831
  const { setEditMode } = usePostListContext();
832
+ const { data: user } = useUser();
801
833
  const onClick = () => {
802
834
  popupState.close();
803
835
  setEditMode({
@@ -808,12 +840,14 @@ function Duplicate({ post, popupState }) {
808
840
  }
809
841
  });
810
842
  };
843
+ const isDisabled = !user?.capabilities?.edit_pages;
811
844
  return /* @__PURE__ */ React15.createElement(
812
845
  ActionMenuItem,
813
846
  {
814
847
  title: (0, import_i18n8.__)("Duplicate", "elementor"),
815
848
  icon: import_icons8.CopyIcon,
816
849
  MenuItemProps: {
850
+ disabled: isDisabled,
817
851
  onClick
818
852
  }
819
853
  }
@@ -831,13 +865,15 @@ function Delete({ post }) {
831
865
  const [isDialogOpen, setIsDialogOpen] = (0, import_react6.useState)(false);
832
866
  const activeDocument = (0, import_editor_documents7.__useActiveDocument)();
833
867
  const isPostActive = activeDocument?.id === post.id;
868
+ const userCanDelete = post.user_can.delete;
869
+ const isDisabled = !userCanDelete || post.isHome || isPostActive;
834
870
  return /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(
835
871
  ActionMenuItem,
836
872
  {
837
873
  title: (0, import_i18n9.__)("Delete", "elementor"),
838
874
  icon: import_icons9.TrashIcon,
839
875
  MenuItemProps: {
840
- disabled: post.isHome || isPostActive,
876
+ disabled: isDisabled,
841
877
  onClick: () => setIsDialogOpen(true),
842
878
  sx: { "&:hover": { color: "error.main" } }
843
879
  }
@@ -902,21 +938,17 @@ var import_icons11 = require("@elementor/icons");
902
938
  var import_i18n11 = require("@wordpress/i18n");
903
939
 
904
940
  // src/hooks/use-homepage-actions.ts
905
- var import_query5 = require("@elementor/query");
941
+ var import_query6 = require("@elementor/query");
906
942
 
907
943
  // src/api/settings.ts
908
- var import_api_fetch4 = __toESM(require("@wordpress/api-fetch"));
944
+ var import_api_fetch5 = __toESM(require("@wordpress/api-fetch"));
909
945
  var getSettings = () => {
910
- const baseUri = "/wp/v2/settings";
911
- const keys = ["show_on_front", "page_on_front"];
912
- const queryParams = new URLSearchParams({
913
- _fields: keys.join(",")
914
- });
915
- const uri = baseUri + "?" + queryParams.toString();
916
- return (0, import_api_fetch4.default)({ path: uri });
946
+ const baseUri = "/elementor/v1/site-navigation/homepage";
947
+ const uri = baseUri;
948
+ return (0, import_api_fetch5.default)({ path: uri });
917
949
  };
918
950
  var updateSettings = (settings) => {
919
- return (0, import_api_fetch4.default)({
951
+ return (0, import_api_fetch5.default)({
920
952
  path: "/wp/v2/settings",
921
953
  method: "POST",
922
954
  data: settings
@@ -924,10 +956,10 @@ var updateSettings = (settings) => {
924
956
  };
925
957
 
926
958
  // src/hooks/use-homepage.ts
927
- var import_query4 = require("@elementor/query");
959
+ var import_query5 = require("@elementor/query");
928
960
  var settingsQueryKey = () => ["site-navigation", "homepage"];
929
961
  function useHomepage() {
930
- return (0, import_query4.useQuery)({
962
+ return (0, import_query5.useQuery)({
931
963
  queryKey: settingsQueryKey(),
932
964
  queryFn: () => getSettings()
933
965
  });
@@ -937,14 +969,14 @@ function useHomepage() {
937
969
  function useHomepageActions() {
938
970
  const invalidateSettings = useInvalidateSettings();
939
971
  const onSuccess = async () => invalidateSettings({ exact: true });
940
- const updateSettingsMutation = (0, import_query5.useMutation)({
972
+ const updateSettingsMutation = (0, import_query6.useMutation)({
941
973
  mutationFn: (settings) => updateSettings(settings),
942
974
  onSuccess
943
975
  });
944
976
  return { updateSettingsMutation };
945
977
  }
946
978
  function useInvalidateSettings() {
947
- const queryClient = (0, import_query5.useQueryClient)();
979
+ const queryClient = (0, import_query6.useQueryClient)();
948
980
  return (options = {}) => {
949
981
  const queryKey = settingsQueryKey();
950
982
  return queryClient.invalidateQueries({ queryKey }, options);
@@ -956,6 +988,7 @@ var import_ui11 = require("@elementor/ui");
956
988
  function SetHome({ post, closeMenu }) {
957
989
  const { updateSettingsMutation } = useHomepageActions();
958
990
  const { setError } = usePostListContext();
991
+ const { data: user } = useUser();
959
992
  const handleClick = async () => {
960
993
  try {
961
994
  await updateSettingsMutation.mutateAsync({ show_on_front: "page", page_on_front: post.id });
@@ -965,13 +998,17 @@ function SetHome({ post, closeMenu }) {
965
998
  closeMenu();
966
999
  }
967
1000
  };
1001
+ const canManageOptions = !!user?.capabilities?.manage_options;
1002
+ const isPostPublished = post.status === "publish";
1003
+ const isPostHomepage = !!post.isHome;
1004
+ const isDisabled = !canManageOptions || isPostHomepage || !isPostPublished || updateSettingsMutation.isPending;
968
1005
  return /* @__PURE__ */ React18.createElement(
969
1006
  ActionMenuItem,
970
1007
  {
971
1008
  title: (0, import_i18n11.__)("Set as homepage", "elementor"),
972
1009
  icon: !updateSettingsMutation.isPending ? import_icons11.HomeIcon : import_ui11.CircularProgress,
973
1010
  MenuItemProps: {
974
- disabled: !!post.isHome || post.status !== "publish" || updateSettingsMutation.isPending,
1011
+ disabled: isDisabled,
975
1012
  onClick: handleClick
976
1013
  }
977
1014
  }
@@ -980,6 +1017,21 @@ function SetHome({ post, closeMenu }) {
980
1017
 
981
1018
  // src/components/panel/posts-list/list-items/list-item-view.tsx
982
1019
  var import_i18n12 = require("@wordpress/i18n");
1020
+ var DisabledPostTooltip = ({ children, isDisabled }) => {
1021
+ if (isDisabled) {
1022
+ const title = /* @__PURE__ */ React19.createElement(import_ui12.Typography, { variant: "caption" }, "You cannot edit this page.", /* @__PURE__ */ React19.createElement("br", null), "To edit it directly, contact the site owner");
1023
+ return /* @__PURE__ */ React19.createElement(
1024
+ import_ui12.Tooltip,
1025
+ {
1026
+ title,
1027
+ placement: "bottom",
1028
+ arrow: false
1029
+ },
1030
+ children
1031
+ );
1032
+ }
1033
+ return /* @__PURE__ */ React19.createElement(React19.Fragment, null, children);
1034
+ };
983
1035
  function ListItemView({ post }) {
984
1036
  const activeDocument = (0, import_editor_documents8.__useActiveDocument)();
985
1037
  const navigateToDocument = (0, import_editor_documents8.__useNavigateToDocument)();
@@ -991,7 +1043,8 @@ function ListItemView({ post }) {
991
1043
  const isActive = activeDocument?.id === post.id;
992
1044
  const status = isActive ? activeDocument?.status.value : post.status;
993
1045
  const title = isActive ? activeDocument?.title : post.title.rendered;
994
- return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(
1046
+ const isDisabled = !post.user_can.edit;
1047
+ return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(DisabledPostTooltip, { isDisabled }, /* @__PURE__ */ React19.createElement(
995
1048
  import_ui12.ListItem,
996
1049
  {
997
1050
  disablePadding: true,
@@ -1009,6 +1062,7 @@ function ListItemView({ post }) {
1009
1062
  import_ui12.ListItemButton,
1010
1063
  {
1011
1064
  selected: isActive,
1065
+ disabled: isDisabled,
1012
1066
  onClick: () => {
1013
1067
  if (!isActive) {
1014
1068
  navigateToDocument(post.id);
@@ -1016,16 +1070,10 @@ function ListItemView({ post }) {
1016
1070
  },
1017
1071
  dense: true
1018
1072
  },
1019
- /* @__PURE__ */ React19.createElement(
1020
- import_ui12.ListItemText,
1021
- {
1022
- disableTypography: true
1023
- },
1024
- /* @__PURE__ */ React19.createElement(PageTitleAndStatus, { title, status })
1025
- ),
1073
+ /* @__PURE__ */ React19.createElement(import_ui12.ListItemText, { disableTypography: true }, /* @__PURE__ */ React19.createElement(PageTitleAndStatus, { title, status })),
1026
1074
  post.isHome && /* @__PURE__ */ React19.createElement(import_icons12.HomeIcon, { titleAccess: (0, import_i18n12.__)("Homepage", "elementor"), color: "disabled" })
1027
1075
  )
1028
- ), /* @__PURE__ */ React19.createElement(
1076
+ )), /* @__PURE__ */ React19.createElement(
1029
1077
  import_ui12.Menu,
1030
1078
  {
1031
1079
  PaperProps: { sx: { mt: 2, width: 200 } },
@@ -1066,11 +1114,13 @@ var import_icons13 = require("@elementor/icons");
1066
1114
  var import_i18n13 = require("@wordpress/i18n");
1067
1115
  function AddNewButton() {
1068
1116
  const { setEditMode } = usePostListContext();
1117
+ const { data: user } = useUser();
1069
1118
  return /* @__PURE__ */ React21.createElement(
1070
1119
  import_ui13.Button,
1071
1120
  {
1072
1121
  size: "small",
1073
1122
  startIcon: /* @__PURE__ */ React21.createElement(import_icons13.PlusIcon, null),
1123
+ disabled: !user?.capabilities?.edit_pages,
1074
1124
  onClick: () => {
1075
1125
  setEditMode({ mode: "create", details: {} });
1076
1126
  },
@@ -1108,7 +1158,7 @@ function ErrorState() {
1108
1158
  function PostsCollapsibleList({ isOpenByDefault = false }) {
1109
1159
  const { type, editMode } = usePostListContext();
1110
1160
  const { data: posts, isLoading: postsLoading, isError: postsError } = usePosts(type);
1111
- const { data: homepageSettings } = useHomepage();
1161
+ const { data: homepageId } = useHomepage();
1112
1162
  if (postsError) {
1113
1163
  return /* @__PURE__ */ React23.createElement(ErrorState, null);
1114
1164
  }
@@ -1124,8 +1174,6 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
1124
1174
  ), /* @__PURE__ */ React23.createElement(import_ui15.Box, null, /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "100%", height: "24px" }), /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" })));
1125
1175
  }
1126
1176
  const label = `${postTypesMap[type].labels.plural_name} (${posts.length.toString()})`;
1127
- const isHomepageSet = homepageSettings?.show_on_front === "page" && !!homepageSettings?.page_on_front;
1128
- const homepageId = isHomepageSet ? homepageSettings.page_on_front : null;
1129
1177
  const mappedPosts = posts.map((post) => {
1130
1178
  if (post.id === homepageId) {
1131
1179
  return { ...post, isHome: true };