@elementor/editor-site-navigation 0.16.0 → 0.17.1

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 CHANGED
@@ -3,6 +3,25 @@
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.17.1](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.17.0...@elementor/editor-site-navigation@0.17.1) (2023-08-01)
7
+
8
+ **Note:** Version bump only for package @elementor/editor-site-navigation
9
+
10
+
11
+
12
+
13
+
14
+ # [0.17.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.16.0...@elementor/editor-site-navigation@0.17.0) (2023-07-30)
15
+
16
+
17
+ ### Features
18
+
19
+ * **site-navigation:** add set as homepage action to pages panel [ED-10865] ([#97](https://github.com/elementor/elementor-packages/issues/97)) ([e947107](https://github.com/elementor/elementor-packages/commit/e947107f29ae43cc1569820a1f6acd6d60fda38e))
20
+
21
+
22
+
23
+
24
+
6
25
  # [0.16.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.15.0...@elementor/editor-site-navigation@0.16.0) (2023-07-30)
7
26
 
8
27
 
package/dist/index.js CHANGED
@@ -250,7 +250,7 @@ function RecentlyEdited() {
250
250
  var import_editor_app_bar = require("@elementor/editor-app-bar");
251
251
 
252
252
  // src/hooks/use-toggle-button-props.ts
253
- var import_i18n14 = require("@wordpress/i18n");
253
+ var import_i18n15 = require("@wordpress/i18n");
254
254
  var import_icons15 = require("@elementor/icons");
255
255
 
256
256
  // src/components/panel/panel.ts
@@ -258,14 +258,14 @@ var import_editor_panels2 = require("@elementor/editor-panels");
258
258
 
259
259
  // src/components/panel/shell.tsx
260
260
  var React23 = __toESM(require("react"));
261
- var import_ui14 = require("@elementor/ui");
261
+ var import_ui15 = require("@elementor/ui");
262
262
  var import_editor_panels = require("@elementor/editor-panels");
263
- var import_i18n13 = require("@wordpress/i18n");
263
+ var import_i18n14 = require("@wordpress/i18n");
264
264
 
265
265
  // src/components/panel/posts-list/posts-collapsible-list.tsx
266
266
  var React21 = __toESM(require("react"));
267
267
  var import_icons13 = require("@elementor/icons");
268
- var import_ui12 = require("@elementor/ui");
268
+ var import_ui13 = require("@elementor/ui");
269
269
 
270
270
  // src/hooks/use-posts.ts
271
271
  var import_query = require("@elementor/query");
@@ -587,7 +587,7 @@ function ListItemDuplicate() {
587
587
 
588
588
  // src/components/panel/posts-list/list-items/list-item-view.tsx
589
589
  var React19 = __toESM(require("react"));
590
- var import_ui11 = require("@elementor/ui");
590
+ var import_ui12 = require("@elementor/ui");
591
591
  var import_icons12 = require("@elementor/icons");
592
592
  var import_editor_documents5 = require("@elementor/editor-documents");
593
593
 
@@ -777,47 +777,105 @@ function View({ post }) {
777
777
  var React18 = __toESM(require("react"));
778
778
  var import_icons11 = require("@elementor/icons");
779
779
  var import_i18n11 = require("@wordpress/i18n");
780
+
781
+ // src/hooks/use-homepage-actions.ts
782
+ var import_query4 = require("@elementor/query");
783
+
784
+ // src/api/settings.ts
785
+ var import_api_fetch4 = __toESM(require("@wordpress/api-fetch"));
786
+ var getSettings = () => {
787
+ const baseUri = "/wp/v2/settings";
788
+ const keys = ["show_on_front", "page_on_front"];
789
+ const queryParams = new URLSearchParams({
790
+ _fields: keys.join(",")
791
+ });
792
+ const uri = baseUri + "?" + queryParams.toString();
793
+ return (0, import_api_fetch4.default)({ path: uri });
794
+ };
795
+ var updateSettings = (settings) => {
796
+ return (0, import_api_fetch4.default)({
797
+ path: "/wp/v2/settings",
798
+ method: "POST",
799
+ data: settings
800
+ });
801
+ };
802
+
803
+ // src/hooks/use-homepage.ts
804
+ var import_query3 = require("@elementor/query");
805
+ var settingsQueryKey = () => ["site-navigation", "homepage"];
806
+ function useHomepage() {
807
+ return (0, import_query3.useQuery)({
808
+ queryKey: settingsQueryKey(),
809
+ queryFn: () => getSettings()
810
+ });
811
+ }
812
+
813
+ // src/hooks/use-homepage-actions.ts
814
+ function useHomepageActions() {
815
+ const invalidateSettings = useInvalidateSettings();
816
+ const onSuccess = async () => invalidateSettings({ exact: true });
817
+ const updateSettingsMutation = (0, import_query4.useMutation)(
818
+ (settings) => updateSettings(settings),
819
+ { onSuccess }
820
+ );
821
+ return { updateSettingsMutation };
822
+ }
823
+ function useInvalidateSettings() {
824
+ const queryClient = (0, import_query4.useQueryClient)();
825
+ return (options = {}) => {
826
+ const queryKey = settingsQueryKey();
827
+ return queryClient.invalidateQueries(queryKey, options);
828
+ };
829
+ }
830
+
831
+ // src/components/panel/actions-menu/actions/set-home.tsx
832
+ var import_ui11 = require("@elementor/ui");
780
833
  function SetHome({ post }) {
834
+ const { updateSettingsMutation } = useHomepageActions();
835
+ const handleClick = () => {
836
+ updateSettingsMutation.mutateAsync({ show_on_front: "page", page_on_front: post.id });
837
+ };
781
838
  return /* @__PURE__ */ React18.createElement(
782
839
  ActionMenuItem,
783
840
  {
784
841
  title: (0, import_i18n11.__)("Set as homepage", "elementor"),
785
- icon: import_icons11.HomeIcon,
842
+ icon: !updateSettingsMutation.isLoading ? import_icons11.HomeIcon : import_ui11.CircularProgress,
786
843
  ListItemButtonProps: {
787
- disabled: !!post.isHome,
788
- onClick: () => null
844
+ disabled: !!post.isHome || post.status !== "publish" || updateSettingsMutation.isLoading,
845
+ onClick: handleClick
789
846
  }
790
847
  }
791
848
  );
792
849
  }
793
850
 
794
851
  // src/components/panel/posts-list/list-items/list-item-view.tsx
852
+ var import_i18n12 = require("@wordpress/i18n");
795
853
  function ListItemView({ post }) {
796
854
  const activeDocument = (0, import_editor_documents5.useActiveDocument)();
797
855
  const navigateToDocument = (0, import_editor_documents5.useNavigateToDocument)();
798
- const popupState = (0, import_ui11.usePopupState)({
856
+ const popupState = (0, import_ui12.usePopupState)({
799
857
  variant: "popover",
800
858
  popupId: "post-actions",
801
859
  disableAutoFocus: true
802
860
  });
803
861
  const isActive = activeDocument?.id === post.id;
804
862
  return /* @__PURE__ */ React19.createElement(React19.Fragment, null, /* @__PURE__ */ React19.createElement(
805
- import_ui11.ListItem,
863
+ import_ui12.ListItem,
806
864
  {
807
865
  disablePadding: true,
808
866
  secondaryAction: /* @__PURE__ */ React19.createElement(
809
- import_ui11.ToggleButton,
867
+ import_ui12.ToggleButton,
810
868
  {
811
869
  value: true,
812
870
  size: "small",
813
871
  selected: popupState.isOpen,
814
- ...(0, import_ui11.bindTrigger)(popupState)
872
+ ...(0, import_ui12.bindTrigger)(popupState)
815
873
  },
816
874
  /* @__PURE__ */ React19.createElement(import_icons12.DotsVerticalIcon, { fontSize: "small" })
817
875
  )
818
876
  },
819
877
  /* @__PURE__ */ React19.createElement(
820
- import_ui11.ListItemButton,
878
+ import_ui12.ListItemButton,
821
879
  {
822
880
  selected: isActive,
823
881
  onClick: () => {
@@ -827,28 +885,28 @@ function ListItemView({ post }) {
827
885
  },
828
886
  dense: true
829
887
  },
830
- /* @__PURE__ */ React19.createElement(import_ui11.ListItemIcon, null),
888
+ /* @__PURE__ */ React19.createElement(import_ui12.ListItemIcon, null),
831
889
  /* @__PURE__ */ React19.createElement(
832
- import_ui11.ListItemText,
890
+ import_ui12.ListItemText,
833
891
  {
834
892
  disableTypography: true
835
893
  },
836
894
  /* @__PURE__ */ React19.createElement(PageTitleAndStatus, { page: post })
837
895
  ),
838
- post.isHome && /* @__PURE__ */ React19.createElement(import_ui11.ListItemIcon, null, /* @__PURE__ */ React19.createElement(import_icons12.HomeIcon, { color: "disabled" }))
896
+ post.isHome && /* @__PURE__ */ React19.createElement(import_ui12.ListItemIcon, null, /* @__PURE__ */ React19.createElement(import_icons12.HomeIcon, { titleAccess: (0, import_i18n12.__)("Homepage", "elementor"), color: "disabled" }))
839
897
  )
840
898
  ), /* @__PURE__ */ React19.createElement(
841
- import_ui11.Menu,
899
+ import_ui12.Menu,
842
900
  {
843
901
  PaperProps: { sx: { mt: 4, width: 200 } },
844
902
  MenuListProps: { dense: true },
845
- ...(0, import_ui11.bindMenu)(popupState)
903
+ ...(0, import_ui12.bindMenu)(popupState)
846
904
  },
847
905
  /* @__PURE__ */ React19.createElement(Rename, { post }),
848
906
  /* @__PURE__ */ React19.createElement(Duplicate, { post, popupState }),
849
907
  /* @__PURE__ */ React19.createElement(Delete, { post }),
850
908
  /* @__PURE__ */ React19.createElement(View, { post }),
851
- /* @__PURE__ */ React19.createElement(import_ui11.Divider, null),
909
+ /* @__PURE__ */ React19.createElement(import_ui12.Divider, null),
852
910
  /* @__PURE__ */ React19.createElement(SetHome, { post })
853
911
  ));
854
912
  }
@@ -875,31 +933,37 @@ function PostListItem2({ post }) {
875
933
  function PostsCollapsibleList({ isOpenByDefault = false }) {
876
934
  const { type, editMode } = usePostListContext();
877
935
  const { data: posts, isLoading: postsLoading } = usePosts(type);
936
+ const { data: homepageSettings } = useHomepage();
878
937
  if (!posts || postsLoading) {
879
- return /* @__PURE__ */ React21.createElement(import_ui12.Box, { spacing: 4, sx: { px: 6 } }, /* @__PURE__ */ React21.createElement(import_ui12.Skeleton, { variant: "text", sx: { fontSize: "2rem" } }), /* @__PURE__ */ React21.createElement(import_ui12.Skeleton, { variant: "rounded", width: "100%", height: "48" }));
938
+ return /* @__PURE__ */ React21.createElement(import_ui13.Box, { spacing: 4, sx: { px: 6 } }, /* @__PURE__ */ React21.createElement(import_ui13.Skeleton, { variant: "text", sx: { fontSize: "2rem" } }), /* @__PURE__ */ React21.createElement(import_ui13.Skeleton, { variant: "rounded", width: "100%", height: "48" }));
880
939
  }
881
940
  const label = `${postTypesMap[type].labels.plural_name} (${posts.length.toString()})`;
882
- return /* @__PURE__ */ React21.createElement(import_ui12.List, { dense: true }, /* @__PURE__ */ React21.createElement(
941
+ const isHomepageSet = homepageSettings?.show_on_front === "page" && !!homepageSettings?.page_on_front;
942
+ const homepageId = isHomepageSet ? homepageSettings.page_on_front : null;
943
+ return /* @__PURE__ */ React21.createElement(import_ui13.List, { dense: true }, /* @__PURE__ */ React21.createElement(
883
944
  CollapsibleList,
884
945
  {
885
946
  label,
886
947
  Icon: import_icons13.PageTypeIcon,
887
948
  isOpenByDefault: isOpenByDefault || false
888
949
  },
889
- posts.map((post) => /* @__PURE__ */ React21.createElement(PostListItem2, { key: post.id, post })),
950
+ posts.map((post) => {
951
+ post = { ...post, isHome: post.id === homepageId };
952
+ return /* @__PURE__ */ React21.createElement(PostListItem2, { key: post.id, post });
953
+ }),
890
954
  ["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React21.createElement(PostListItem2, null)
891
955
  ));
892
956
  }
893
957
 
894
958
  // src/components/panel/add-new-button.tsx
895
959
  var React22 = __toESM(require("react"));
896
- var import_ui13 = require("@elementor/ui");
960
+ var import_ui14 = require("@elementor/ui");
897
961
  var import_icons14 = require("@elementor/icons");
898
- var import_i18n12 = require("@wordpress/i18n");
962
+ var import_i18n13 = require("@wordpress/i18n");
899
963
  function AddNewButton() {
900
964
  const { setEditMode } = usePostListContext();
901
965
  return /* @__PURE__ */ React22.createElement(
902
- import_ui13.Button,
966
+ import_ui14.Button,
903
967
  {
904
968
  size: "small",
905
969
  sx: { mt: 4, mb: 4, mr: 5 },
@@ -908,14 +972,14 @@ function AddNewButton() {
908
972
  setEditMode({ mode: "create", details: {} });
909
973
  }
910
974
  },
911
- (0, import_i18n12.__)("Add New", "elementor")
975
+ (0, import_i18n13.__)("Add New", "elementor")
912
976
  );
913
977
  }
914
978
 
915
979
  // src/components/panel/shell.tsx
916
980
  var Shell = () => {
917
- return /* @__PURE__ */ React23.createElement(import_editor_panels.Panel, null, /* @__PURE__ */ React23.createElement(import_editor_panels.PanelHeader, null, /* @__PURE__ */ React23.createElement(import_editor_panels.PanelHeaderTitle, null, (0, import_i18n13.__)("Pages", "elementor"))), /* @__PURE__ */ React23.createElement(import_editor_panels.PanelBody, null, /* @__PURE__ */ React23.createElement(PostListContextProvider, { type: "page" }, /* @__PURE__ */ React23.createElement(
918
- import_ui14.Box,
981
+ return /* @__PURE__ */ React23.createElement(import_editor_panels.Panel, null, /* @__PURE__ */ React23.createElement(import_editor_panels.PanelHeader, null, /* @__PURE__ */ React23.createElement(import_editor_panels.PanelHeaderTitle, null, (0, import_i18n14.__)("Pages", "elementor"))), /* @__PURE__ */ React23.createElement(import_editor_panels.PanelBody, null, /* @__PURE__ */ React23.createElement(PostListContextProvider, { type: "page" }, /* @__PURE__ */ React23.createElement(
982
+ import_ui15.Box,
919
983
  {
920
984
  display: "flex",
921
985
  justifyContent: "flex-end",
@@ -941,7 +1005,7 @@ function useToggleButtonProps() {
941
1005
  const { isOpen, isBlocked } = usePanelStatus();
942
1006
  const { open, close } = usePanelActions();
943
1007
  return {
944
- title: (0, import_i18n14.__)("Pages", "elementor"),
1008
+ title: (0, import_i18n15.__)("Pages", "elementor"),
945
1009
  icon: import_icons15.PagesIcon,
946
1010
  onClick: () => isOpen ? close() : open(),
947
1011
  selected: isOpen,
package/dist/index.mjs CHANGED
@@ -232,7 +232,7 @@ function RecentlyEdited() {
232
232
  import { injectIntoPageIndication, toolsMenu } from "@elementor/editor-app-bar";
233
233
 
234
234
  // src/hooks/use-toggle-button-props.ts
235
- import { __ as __14 } from "@wordpress/i18n";
235
+ import { __ as __15 } from "@wordpress/i18n";
236
236
  import { PagesIcon } from "@elementor/icons";
237
237
 
238
238
  // src/components/panel/panel.ts
@@ -242,7 +242,7 @@ import { createPanel } from "@elementor/editor-panels";
242
242
  import * as React23 from "react";
243
243
  import { Box as Box5 } from "@elementor/ui";
244
244
  import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from "@elementor/editor-panels";
245
- import { __ as __13 } from "@wordpress/i18n";
245
+ import { __ as __14 } from "@wordpress/i18n";
246
246
 
247
247
  // src/components/panel/posts-list/posts-collapsible-list.tsx
248
248
  import * as React21 from "react";
@@ -786,21 +786,79 @@ function View({ post }) {
786
786
  import * as React18 from "react";
787
787
  import { HomeIcon } from "@elementor/icons";
788
788
  import { __ as __11 } from "@wordpress/i18n";
789
+
790
+ // src/hooks/use-homepage-actions.ts
791
+ import { useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@elementor/query";
792
+
793
+ // src/api/settings.ts
794
+ import apiFetch4 from "@wordpress/api-fetch";
795
+ var getSettings = () => {
796
+ const baseUri = "/wp/v2/settings";
797
+ const keys = ["show_on_front", "page_on_front"];
798
+ const queryParams = new URLSearchParams({
799
+ _fields: keys.join(",")
800
+ });
801
+ const uri = baseUri + "?" + queryParams.toString();
802
+ return apiFetch4({ path: uri });
803
+ };
804
+ var updateSettings = (settings) => {
805
+ return apiFetch4({
806
+ path: "/wp/v2/settings",
807
+ method: "POST",
808
+ data: settings
809
+ });
810
+ };
811
+
812
+ // src/hooks/use-homepage.ts
813
+ import { useQuery as useQuery2 } from "@elementor/query";
814
+ var settingsQueryKey = () => ["site-navigation", "homepage"];
815
+ function useHomepage() {
816
+ return useQuery2({
817
+ queryKey: settingsQueryKey(),
818
+ queryFn: () => getSettings()
819
+ });
820
+ }
821
+
822
+ // src/hooks/use-homepage-actions.ts
823
+ function useHomepageActions() {
824
+ const invalidateSettings = useInvalidateSettings();
825
+ const onSuccess = async () => invalidateSettings({ exact: true });
826
+ const updateSettingsMutation = useMutation2(
827
+ (settings) => updateSettings(settings),
828
+ { onSuccess }
829
+ );
830
+ return { updateSettingsMutation };
831
+ }
832
+ function useInvalidateSettings() {
833
+ const queryClient = useQueryClient2();
834
+ return (options = {}) => {
835
+ const queryKey = settingsQueryKey();
836
+ return queryClient.invalidateQueries(queryKey, options);
837
+ };
838
+ }
839
+
840
+ // src/components/panel/actions-menu/actions/set-home.tsx
841
+ import { CircularProgress as CircularProgress4 } from "@elementor/ui";
789
842
  function SetHome({ post }) {
843
+ const { updateSettingsMutation } = useHomepageActions();
844
+ const handleClick = () => {
845
+ updateSettingsMutation.mutateAsync({ show_on_front: "page", page_on_front: post.id });
846
+ };
790
847
  return /* @__PURE__ */ React18.createElement(
791
848
  ActionMenuItem,
792
849
  {
793
850
  title: __11("Set as homepage", "elementor"),
794
- icon: HomeIcon,
851
+ icon: !updateSettingsMutation.isLoading ? HomeIcon : CircularProgress4,
795
852
  ListItemButtonProps: {
796
- disabled: !!post.isHome,
797
- onClick: () => null
853
+ disabled: !!post.isHome || post.status !== "publish" || updateSettingsMutation.isLoading,
854
+ onClick: handleClick
798
855
  }
799
856
  }
800
857
  );
801
858
  }
802
859
 
803
860
  // src/components/panel/posts-list/list-items/list-item-view.tsx
861
+ import { __ as __12 } from "@wordpress/i18n";
804
862
  function ListItemView({ post }) {
805
863
  const activeDocument = useActiveDocument3();
806
864
  const navigateToDocument = useNavigateToDocument3();
@@ -844,7 +902,7 @@ function ListItemView({ post }) {
844
902
  },
845
903
  /* @__PURE__ */ React19.createElement(PageTitleAndStatus, { page: post })
846
904
  ),
847
- post.isHome && /* @__PURE__ */ React19.createElement(ListItemIcon5, null, /* @__PURE__ */ React19.createElement(HomeIcon2, { color: "disabled" }))
905
+ post.isHome && /* @__PURE__ */ React19.createElement(ListItemIcon5, null, /* @__PURE__ */ React19.createElement(HomeIcon2, { titleAccess: __12("Homepage", "elementor"), color: "disabled" }))
848
906
  )
849
907
  ), /* @__PURE__ */ React19.createElement(
850
908
  Menu2,
@@ -884,10 +942,13 @@ function PostListItem2({ post }) {
884
942
  function PostsCollapsibleList({ isOpenByDefault = false }) {
885
943
  const { type, editMode } = usePostListContext();
886
944
  const { data: posts, isLoading: postsLoading } = usePosts(type);
945
+ const { data: homepageSettings } = useHomepage();
887
946
  if (!posts || postsLoading) {
888
947
  return /* @__PURE__ */ React21.createElement(Box4, { spacing: 4, sx: { px: 6 } }, /* @__PURE__ */ React21.createElement(Skeleton, { variant: "text", sx: { fontSize: "2rem" } }), /* @__PURE__ */ React21.createElement(Skeleton, { variant: "rounded", width: "100%", height: "48" }));
889
948
  }
890
949
  const label = `${postTypesMap[type].labels.plural_name} (${posts.length.toString()})`;
950
+ const isHomepageSet = homepageSettings?.show_on_front === "page" && !!homepageSettings?.page_on_front;
951
+ const homepageId = isHomepageSet ? homepageSettings.page_on_front : null;
891
952
  return /* @__PURE__ */ React21.createElement(List2, { dense: true }, /* @__PURE__ */ React21.createElement(
892
953
  CollapsibleList,
893
954
  {
@@ -895,7 +956,10 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
895
956
  Icon: PageTypeIcon2,
896
957
  isOpenByDefault: isOpenByDefault || false
897
958
  },
898
- posts.map((post) => /* @__PURE__ */ React21.createElement(PostListItem2, { key: post.id, post })),
959
+ posts.map((post) => {
960
+ post = { ...post, isHome: post.id === homepageId };
961
+ return /* @__PURE__ */ React21.createElement(PostListItem2, { key: post.id, post });
962
+ }),
899
963
  ["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React21.createElement(PostListItem2, null)
900
964
  ));
901
965
  }
@@ -904,7 +968,7 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
904
968
  import * as React22 from "react";
905
969
  import { Button as Button3 } from "@elementor/ui";
906
970
  import { PlusIcon as PlusIcon2 } from "@elementor/icons";
907
- import { __ as __12 } from "@wordpress/i18n";
971
+ import { __ as __13 } from "@wordpress/i18n";
908
972
  function AddNewButton() {
909
973
  const { setEditMode } = usePostListContext();
910
974
  return /* @__PURE__ */ React22.createElement(
@@ -917,13 +981,13 @@ function AddNewButton() {
917
981
  setEditMode({ mode: "create", details: {} });
918
982
  }
919
983
  },
920
- __12("Add New", "elementor")
984
+ __13("Add New", "elementor")
921
985
  );
922
986
  }
923
987
 
924
988
  // src/components/panel/shell.tsx
925
989
  var Shell = () => {
926
- return /* @__PURE__ */ React23.createElement(Panel, null, /* @__PURE__ */ React23.createElement(PanelHeader, null, /* @__PURE__ */ React23.createElement(PanelHeaderTitle, null, __13("Pages", "elementor"))), /* @__PURE__ */ React23.createElement(PanelBody, null, /* @__PURE__ */ React23.createElement(PostListContextProvider, { type: "page" }, /* @__PURE__ */ React23.createElement(
990
+ return /* @__PURE__ */ React23.createElement(Panel, null, /* @__PURE__ */ React23.createElement(PanelHeader, null, /* @__PURE__ */ React23.createElement(PanelHeaderTitle, null, __14("Pages", "elementor"))), /* @__PURE__ */ React23.createElement(PanelBody, null, /* @__PURE__ */ React23.createElement(PostListContextProvider, { type: "page" }, /* @__PURE__ */ React23.createElement(
927
991
  Box5,
928
992
  {
929
993
  display: "flex",
@@ -950,7 +1014,7 @@ function useToggleButtonProps() {
950
1014
  const { isOpen, isBlocked } = usePanelStatus();
951
1015
  const { open, close } = usePanelActions();
952
1016
  return {
953
- title: __14("Pages", "elementor"),
1017
+ title: __15("Pages", "elementor"),
954
1018
  icon: PagesIcon,
955
1019
  onClick: () => isOpen ? close() : open(),
956
1020
  selected: isOpen,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-site-navigation",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -32,11 +32,11 @@
32
32
  "dev": "tsup --config=../../tsup.dev.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@elementor/editor-app-bar": "^0.6.9",
35
+ "@elementor/editor-app-bar": "^0.7.0",
36
36
  "@elementor/editor-documents": "^0.8.3",
37
- "@elementor/editor-panels": "^0.1.5",
37
+ "@elementor/editor-panels": "^0.1.6",
38
38
  "@elementor/env": "^0.2.0",
39
- "@elementor/icons": "^0.6.1",
39
+ "@elementor/icons": "^0.7.0",
40
40
  "@elementor/query": "^0.1.1",
41
41
  "@elementor/ui": "^1.4.53",
42
42
  "@wordpress/api-fetch": "^6.33.0",
@@ -49,5 +49,5 @@
49
49
  "elementor": {
50
50
  "type": "extension"
51
51
  },
52
- "gitHead": "673479dfda4fe5feae87a229e7b371b8fb19c0ee"
52
+ "gitHead": "3a35a0487b8a72853b7f307fed47fd72ea311d06"
53
53
  }
@@ -0,0 +1,28 @@
1
+ import apiFetch from '@wordpress/api-fetch';
2
+
3
+ export type Settings = {
4
+ show_on_front: 'page' | '',
5
+ page_on_front: number,
6
+ }
7
+
8
+ export const getSettings = () => {
9
+ const baseUri = '/wp/v2/settings';
10
+
11
+ const keys: Array<keyof Settings> = [ 'show_on_front', 'page_on_front' ];
12
+
13
+ const queryParams = new URLSearchParams( {
14
+ _fields: keys.join( ',' ),
15
+ } );
16
+
17
+ const uri = baseUri + '?' + queryParams.toString();
18
+
19
+ return apiFetch<Settings>( { path: uri } );
20
+ };
21
+
22
+ export const updateSettings = ( settings: Settings ) => {
23
+ return apiFetch( {
24
+ path: '/wp/v2/settings',
25
+ method: 'POST',
26
+ data: settings,
27
+ } );
28
+ };
@@ -0,0 +1,67 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
+ import SetHome from '../set-home';
3
+ import * as React from 'react';
4
+ import { Post } from '../../../../../types';
5
+
6
+ const mockMutateAsync = jest.fn();
7
+ jest.mock( '../../../../../hooks/use-homepage-actions', () => ( {
8
+ __esModule: true,
9
+ useHomepageActions: jest.fn( () => ( {
10
+ updateSettingsMutation: {
11
+ mutateAsync: mockMutateAsync,
12
+ isLoading: false,
13
+ },
14
+ } ) ),
15
+ } ) );
16
+
17
+ describe( '@elementor/editor-site-navigation - SetHome', () => {
18
+ afterAll( () => {
19
+ jest.clearAllMocks();
20
+ } );
21
+
22
+ it( 'should render Set as homepage', () => {
23
+ // Arrange.
24
+ const post: Post = {
25
+ id: 1,
26
+ title: {
27
+ rendered: 'Test Page',
28
+ },
29
+ status: 'publish',
30
+ type: 'page',
31
+ link: 'https://example.local/test-page',
32
+ isHome: false,
33
+ };
34
+
35
+ // Act.
36
+ render( <SetHome post={ post } /> );
37
+
38
+ // Assert.
39
+ const button = screen.getByRole( 'button' );
40
+ expect( button ).not.toHaveAttribute( 'aria-disabled' );
41
+
42
+ fireEvent.click( button );
43
+
44
+ expect( mockMutateAsync ).toHaveBeenCalledTimes( 1 );
45
+ } );
46
+
47
+ it( 'should render Set as homepage disabled when the page status is draft', () => {
48
+ // Arrange.
49
+ const post: Post = {
50
+ id: 1,
51
+ title: {
52
+ rendered: 'Test Page',
53
+ },
54
+ status: 'draft',
55
+ type: 'page',
56
+ link: 'https://example.local/test-page',
57
+ isHome: false,
58
+ };
59
+
60
+ // Act.
61
+ render( <SetHome post={ post } /> );
62
+
63
+ // Assert.
64
+ const button = screen.getByRole( 'button' );
65
+ expect( button ).toHaveAttribute( 'aria-disabled', 'true' );
66
+ } );
67
+ } );
@@ -3,16 +3,24 @@ import { Post } from '../../../../types';
3
3
  import { HomeIcon } from '@elementor/icons';
4
4
  import { __ } from '@wordpress/i18n';
5
5
  import ActionMenuItem from '../action-menu-item';
6
+ import { useHomepageActions } from '../../../../hooks/use-homepage-actions';
7
+ import { CircularProgress } from '@elementor/ui';
6
8
 
7
9
  export default function SetHome( { post }: { post: Post } ) {
10
+ const { updateSettingsMutation } = useHomepageActions();
11
+
12
+ const handleClick = () => {
13
+ updateSettingsMutation.mutateAsync( { show_on_front: 'page', page_on_front: post.id } );
14
+ };
15
+
8
16
  return (
9
17
  <ActionMenuItem
10
18
  title={ __( 'Set as homepage', 'elementor' ) }
11
- icon={ HomeIcon }
19
+ icon={ ! updateSettingsMutation.isLoading ? HomeIcon : CircularProgress }
12
20
  ListItemButtonProps={
13
21
  {
14
- disabled: !! post.isHome,
15
- onClick: () => null,
22
+ disabled: !! post.isHome || post.status !== 'publish' || updateSettingsMutation.isLoading,
23
+ onClick: handleClick,
16
24
  }
17
25
  }
18
26
  />
@@ -8,13 +8,23 @@ jest.mock( '../../../../hooks/use-posts', () => ( {
8
8
  usePosts: jest.fn( () => ( {
9
9
  isLoading: false,
10
10
  data: [
11
- { id: 1, type: 'page', title: { rendered: 'Home' }, status: 'draft', link: 'www.test.demo' },
12
- { id: 2, type: 'page', title: { rendered: 'About' }, status: 'publish', link: 'www.test.demo' },
13
- { id: 3, type: 'page', title: { rendered: 'Services' }, status: 'publish', link: 'www.test.demo', isHome: true },
11
+ { id: 1, type: 'page', title: { rendered: 'Home' }, status: 'publish', link: 'www.test.demo' },
12
+ { id: 2, type: 'page', title: { rendered: 'About' }, status: 'draft', link: 'www.test.demo' },
14
13
  ],
15
14
  } ) ),
16
15
  } ) );
17
16
 
17
+ jest.mock( '../../../../hooks/use-homepage', () => ( {
18
+ __esModule: true,
19
+ useHomepage: jest.fn( () => ( {
20
+ isLoading: false,
21
+ data: {
22
+ show_on_front: 'page',
23
+ page_on_front: 1,
24
+ },
25
+ } ) ),
26
+ } ) );
27
+
18
28
  jest.mock( '@elementor/editor-documents', () => ( {
19
29
  __esModule: true,
20
30
  useActiveDocument: jest.fn( () => ( {
@@ -33,22 +43,27 @@ describe( '@elementor/editor-site-navigation - PostsCollapsibleList', () => {
33
43
  renderWithQuery( <PostsCollapsibleList isOpenByDefault={ false } /> );
34
44
 
35
45
  // Assert.
36
- const label = screen.getByText( `Pages (3)` );
46
+ const label = screen.getByText( `Pages (2)` );
37
47
  expect( label ).toBeInTheDocument();
38
48
 
39
- const postInList = screen.queryByText( 'Services' );
49
+ const postInList = screen.queryByText( 'Home' );
40
50
  expect( postInList ).not.toBeInTheDocument();
41
51
  } );
42
52
 
43
- it( 'should render open list', () => {
53
+ it( 'should render open list with home icon and page status', () => {
44
54
  // Act.
45
55
  renderWithQuery( <PostsCollapsibleList isOpenByDefault={ true } /> );
46
56
 
47
57
  // Assert.
48
- const label = screen.getByText( `Pages (3)` );
49
- expect( label ).toBeInTheDocument();
58
+ const items = screen.getAllByRole( 'listitem' );
59
+
60
+ expect( items.length ).toBe( 3 );
50
61
 
51
- const postInList = screen.getByText( 'Services' );
52
- expect( postInList ).toBeInTheDocument();
62
+ // First item is the list title.
63
+ expect( items[ 0 ] ).toHaveTextContent( `Pages (2)` );
64
+ expect( items[ 1 ] ).toHaveTextContent( 'Home' );
65
+ expect( items[ 1 ] ).toHaveTextContent( 'Homepage' ); // Home icon.
66
+ expect( items[ 2 ] ).toHaveTextContent( 'About' );
67
+ expect( items[ 2 ] ).toHaveTextContent( '(draft)' );
53
68
  } );
54
69
  } );
@@ -20,6 +20,7 @@ import Delete from '../../actions-menu/actions/delete';
20
20
  import View from '../../actions-menu/actions/view';
21
21
  import SetHome from '../../actions-menu/actions/set-home';
22
22
  import { Post } from '../../../../types';
23
+ import { __ } from '@wordpress/i18n';
23
24
 
24
25
  export default function ListItemView( { post }: { post: Post } ) {
25
26
  const activeDocument = useActiveDocument();
@@ -65,7 +66,7 @@ export default function ListItemView( { post }: { post: Post } ) {
65
66
  </ListItemText>
66
67
  { post.isHome &&
67
68
  <ListItemIcon>
68
- <HomeIcon color="disabled" />
69
+ <HomeIcon titleAccess={ __( 'Homepage', 'elementor' ) } color="disabled" />
69
70
  </ListItemIcon>
70
71
  }
71
72
  </ListItemButton>
@@ -6,6 +6,7 @@ import { usePostListContext } from '../../../contexts/post-list-context';
6
6
  import { postTypesMap } from '../../../api/post';
7
7
  import CollapsibleList from './collapsible-list';
8
8
  import PostListItem from './post-list-item';
9
+ import { useHomepage } from '../../../hooks/use-homepage';
9
10
 
10
11
  type Props = {
11
12
  isOpenByDefault?: boolean,
@@ -14,6 +15,7 @@ type Props = {
14
15
  export default function PostsCollapsibleList( { isOpenByDefault = false }: Props ) {
15
16
  const { type, editMode } = usePostListContext();
16
17
  const { data: posts, isLoading: postsLoading } = usePosts( type );
18
+ const { data: homepageSettings } = useHomepage();
17
19
 
18
20
  if ( ! posts || postsLoading ) {
19
21
  return (
@@ -26,6 +28,9 @@ export default function PostsCollapsibleList( { isOpenByDefault = false }: Props
26
28
 
27
29
  const label = `${ postTypesMap[ type ].labels.plural_name } (${ posts.length.toString() })`;
28
30
 
31
+ const isHomepageSet = homepageSettings?.show_on_front === 'page' && !! homepageSettings?.page_on_front;
32
+ const homepageId = isHomepageSet ? homepageSettings.page_on_front : null;
33
+
29
34
  return (
30
35
  <List dense>
31
36
  <CollapsibleList
@@ -33,7 +38,10 @@ export default function PostsCollapsibleList( { isOpenByDefault = false }: Props
33
38
  Icon={ PageTypeIcon }
34
39
  isOpenByDefault={ isOpenByDefault || false }
35
40
  >
36
- { posts.map( ( post ) => <PostListItem key={ post.id } post={ post } /> ) }
41
+ { posts.map( ( post ) => {
42
+ post = { ...post, isHome: post.id === homepageId };
43
+ return <PostListItem key={ post.id } post={ post } />;
44
+ } ) }
37
45
  {
38
46
  [ 'duplicate', 'create' ].includes( editMode.mode ) &&
39
47
  <PostListItem />
@@ -0,0 +1,43 @@
1
+ import apiFetch from '@wordpress/api-fetch';
2
+ import { renderHookWithQuery } from 'test-utils';
3
+ import { useHomepageActions } from '../use-homepage-actions';
4
+ import { settingsQueryKey } from '../use-homepage';
5
+
6
+ jest.mock( '@wordpress/api-fetch' );
7
+
8
+ describe( '@elementor/site-settings/use-homepage-actions', () => {
9
+ beforeEach( () => {
10
+ jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( {} ) );
11
+ } );
12
+
13
+ afterEach( () => {
14
+ jest.clearAllMocks();
15
+ } );
16
+
17
+ it( 'should run updateSettings from useHomepageActions hook', async () => {
18
+ // Arrange.
19
+ const { component, queryClient } = renderHookWithQuery( () => useHomepageActions() );
20
+ const { updateSettingsMutation } = component.result.current;
21
+
22
+ const queryKey = settingsQueryKey();
23
+ await queryClient.setQueryData( queryKey, {
24
+ show_on_front: '',
25
+ page_on_front: 0,
26
+ } );
27
+
28
+ // Act.
29
+ await updateSettingsMutation.mutateAsync( { show_on_front: 'page', page_on_front: 1 } );
30
+
31
+ expect( apiFetch ).toHaveBeenCalledTimes( 1 );
32
+ expect( apiFetch ).toHaveBeenCalledWith( {
33
+ path: '/wp/v2/settings',
34
+ method: 'POST',
35
+ data: {
36
+ show_on_front: 'page',
37
+ page_on_front: 1,
38
+ },
39
+ } );
40
+
41
+ expect( queryClient.getQueryState( queryKey )?.isInvalidated ).toBe( true );
42
+ } );
43
+ } );
@@ -0,0 +1,42 @@
1
+ import { waitFor } from '@testing-library/react';
2
+ import apiFetch from '@wordpress/api-fetch';
3
+ import { useHomepage } from '../use-homepage';
4
+ import { renderHookWithQuery } from 'test-utils';
5
+
6
+ jest.mock( '@wordpress/api-fetch' );
7
+
8
+ describe( '@elementor/site-settings/use-homepage', () => {
9
+ beforeEach( () => {
10
+ jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( [] ) );
11
+ } );
12
+
13
+ afterEach( () => {
14
+ jest.clearAllMocks();
15
+ } );
16
+
17
+ it( 'useHomepage hook should return homepage settings', async () => {
18
+ // Arrange.
19
+ const settings = {
20
+ show_on_front: 'page',
21
+ page_on_front: 1,
22
+ };
23
+ jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( settings ) );
24
+
25
+ // Act.
26
+ const { component } = renderHookWithQuery( () => useHomepage() );
27
+
28
+ // Assert.
29
+ const expectedPath = `/wp/v2/settings?_fields=${ encodeURIComponent( 'show_on_front,page_on_front' ) }`;
30
+
31
+ expect( apiFetch ).toHaveBeenCalledWith( {
32
+ path: expectedPath,
33
+ } );
34
+ expect( apiFetch ).toHaveBeenCalledTimes( 1 );
35
+
36
+ await waitFor( () => {
37
+ return component.result.current.isSuccess;
38
+ } );
39
+
40
+ expect( component.result.current.data ).toBe( settings );
41
+ } );
42
+ } );
@@ -0,0 +1,26 @@
1
+ import { useMutation, useQueryClient } from '@elementor/query';
2
+ import { Settings, updateSettings } from '../api/settings';
3
+ import { settingsQueryKey } from './use-homepage';
4
+
5
+ export function useHomepageActions() {
6
+ const invalidateSettings = useInvalidateSettings();
7
+
8
+ const onSuccess = async () => invalidateSettings( { exact: true } );
9
+
10
+ const updateSettingsMutation = useMutation(
11
+ ( settings: Settings ) => updateSettings( settings ),
12
+ { onSuccess }
13
+ );
14
+
15
+ return { updateSettingsMutation };
16
+ }
17
+
18
+ function useInvalidateSettings() {
19
+ const queryClient = useQueryClient();
20
+
21
+ return ( options = {} ) => {
22
+ const queryKey = settingsQueryKey();
23
+
24
+ return queryClient.invalidateQueries( queryKey, options );
25
+ };
26
+ }
@@ -0,0 +1,11 @@
1
+ import { useQuery } from '@elementor/query';
2
+ import { getSettings } from '../api/settings';
3
+
4
+ export const settingsQueryKey = () => [ 'site-navigation', 'homepage' ];
5
+
6
+ export function useHomepage( ) {
7
+ return useQuery( {
8
+ queryKey: settingsQueryKey(),
9
+ queryFn: () => getSettings(),
10
+ } );
11
+ }
package/src/types.ts CHANGED
@@ -8,13 +8,3 @@ export type Post = {
8
8
  rendered: string;
9
9
  }
10
10
  };
11
-
12
- export type PostType = {
13
- name: string;
14
- slug: string;
15
- labels: Record<string, string>;
16
- rest_base: string;
17
- rest_namespace: string;
18
- }
19
-
20
- export type NonNullableQueryResponse<T> = T extends { data: infer U } ? T & { data: NonNullable<U> } : T;