@elementor/editor-site-navigation 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/index.js +65 -32
- package/dist/index.mjs +65 -32
- package/package.json +3 -2
- package/src/api/post.ts +64 -0
- package/src/components/panel/actions-menu/action-list-item.tsx +2 -2
- package/src/components/panel/actions-menu/page-actions-menu.tsx +2 -2
- package/src/components/panel/add-new-page-button.tsx +15 -0
- package/src/components/panel/pages-actions/delete.tsx +2 -2
- package/src/components/panel/pages-actions/set-home.tsx +2 -2
- package/src/components/panel/pages-list/__tests__/page-list-item.test.tsx +26 -14
- package/src/components/panel/pages-list/__tests__/pages-collapsible-list.test.tsx +24 -15
- package/src/components/panel/pages-list/page-list-item.tsx +2 -2
- package/src/components/panel/pages-list/pages-collapsible-list.tsx +17 -4
- package/src/components/panel/shell.tsx +4 -24
- package/src/components/shared/page-title-and-status.tsx +3 -3
- package/src/hooks/__tests__/use-post-actions.test.ts +89 -0
- package/src/hooks/__tests__/use-posts.test.ts +45 -0
- package/src/hooks/use-posts-actions.ts +40 -0
- package/src/hooks/use-posts.ts +9 -121
- package/src/types.ts +16 -3
- package/src/hooks/types/interfaces.ts +0 -13
- package/src/hooks/use-post-types.ts +0 -64
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.10.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.9.2...@elementor/editor-site-navigation@0.10.0) (2023-07-02)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **site-navigation:** add data fetching with react query [ED-10872] ([#68](https://github.com/elementor/elementor-packages/issues/68)) ([b6ae743](https://github.com/elementor/elementor-packages/commit/b6ae7437b7f162b21898d52164d0b04769d52bb9))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## [0.9.2](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.9.1...@elementor/editor-site-navigation@0.9.2) (2023-06-29)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @elementor/editor-site-navigation
|
package/dist/index.js
CHANGED
|
@@ -250,16 +250,15 @@ function RecentlyEdited() {
|
|
|
250
250
|
var import_editor_app_bar = require("@elementor/editor-app-bar");
|
|
251
251
|
|
|
252
252
|
// src/hooks/useToggleButtonProps.ts
|
|
253
|
-
var
|
|
253
|
+
var import_i18n10 = require("@wordpress/i18n");
|
|
254
254
|
var import_icons14 = require("@elementor/icons");
|
|
255
255
|
|
|
256
256
|
// src/components/panel/panel.ts
|
|
257
257
|
var import_editor_panels2 = require("@elementor/editor-panels");
|
|
258
258
|
|
|
259
259
|
// src/components/panel/shell.tsx
|
|
260
|
-
var
|
|
261
|
-
var
|
|
262
|
-
var import_icons13 = require("@elementor/icons");
|
|
260
|
+
var React19 = __toESM(require("react"));
|
|
261
|
+
var import_ui14 = require("@elementor/ui");
|
|
263
262
|
var import_editor_panels = require("@elementor/editor-panels");
|
|
264
263
|
|
|
265
264
|
// src/components/panel/pages-list/pages-collapsible-list.tsx
|
|
@@ -306,6 +305,7 @@ function CollapsibleList({
|
|
|
306
305
|
|
|
307
306
|
// src/components/panel/pages-list/pages-collapsible-list.tsx
|
|
308
307
|
var import_icons12 = require("@elementor/icons");
|
|
308
|
+
var import_ui12 = require("@elementor/ui");
|
|
309
309
|
|
|
310
310
|
// src/components/panel/pages-list/page-list-item.tsx
|
|
311
311
|
var React16 = __toESM(require("react"));
|
|
@@ -353,7 +353,7 @@ var PageTitle = ({ title }) => {
|
|
|
353
353
|
);
|
|
354
354
|
};
|
|
355
355
|
function PageTitleAndStatus({ page }) {
|
|
356
|
-
return /* @__PURE__ */ React7.createElement(import_ui7.Box, { display: "flex" }, /* @__PURE__ */ React7.createElement(PageTitle, { title: page.title }), "\xA0", /* @__PURE__ */ React7.createElement(PageStatus, { status: page.status }));
|
|
356
|
+
return /* @__PURE__ */ React7.createElement(import_ui7.Box, { display: "flex" }, /* @__PURE__ */ React7.createElement(PageTitle, { title: page.title.rendered }), "\xA0", /* @__PURE__ */ React7.createElement(PageStatus, { status: page.status }));
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
// src/components/panel/actions-menu/page-actions-menu.tsx
|
|
@@ -529,9 +529,43 @@ function PageListItem({ page }) {
|
|
|
529
529
|
), /* @__PURE__ */ React16.createElement(PageActionsMenu, { page, ...(0, import_ui11.bindMenu)(popupState) }));
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
// src/hooks/use-posts.ts
|
|
533
|
+
var import_query = require("@elementor/query");
|
|
534
|
+
|
|
535
|
+
// src/api/post.ts
|
|
536
|
+
var import_api_fetch3 = __toESM(require("@wordpress/api-fetch"));
|
|
537
|
+
var postTypesMap = {
|
|
538
|
+
page: "pages"
|
|
539
|
+
};
|
|
540
|
+
var getRequest = (postTypeSlug) => {
|
|
541
|
+
const baseUri = `/wp/v2/${postTypesMap[postTypeSlug]}`;
|
|
542
|
+
const keys = ["id", "type", "title", "link", "status"];
|
|
543
|
+
const queryParams = new URLSearchParams({
|
|
544
|
+
status: "any",
|
|
545
|
+
per_page: "-1",
|
|
546
|
+
_fields: keys.join(",")
|
|
547
|
+
});
|
|
548
|
+
const uri = baseUri + "?" + queryParams.toString();
|
|
549
|
+
return (0, import_api_fetch3.default)({ path: uri });
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// src/hooks/use-posts.ts
|
|
553
|
+
var postsQueryKey = (postTypeSlug) => ["site-navigation", "posts", postTypeSlug];
|
|
554
|
+
function usePosts(postTypeSlug) {
|
|
555
|
+
return (0, import_query.useQuery)({
|
|
556
|
+
queryKey: postsQueryKey(postTypeSlug),
|
|
557
|
+
queryFn: () => getRequest(postTypeSlug)
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
532
561
|
// src/components/panel/pages-list/pages-collapsible-list.tsx
|
|
533
|
-
|
|
534
|
-
|
|
562
|
+
var import_i18n8 = require("@wordpress/i18n");
|
|
563
|
+
function PagesCollapsibleList({ isOpenByDefault = false }) {
|
|
564
|
+
const { data: pages, isLoading: pagesLoading } = usePosts("page");
|
|
565
|
+
if (!pages || pagesLoading) {
|
|
566
|
+
return /* @__PURE__ */ React17.createElement(import_ui12.Box, { spacing: 4, sx: { px: 6 } }, /* @__PURE__ */ React17.createElement(import_ui12.Skeleton, { variant: "text", sx: { fontSize: "2rem" } }), /* @__PURE__ */ React17.createElement(import_ui12.Skeleton, { variant: "rounded", width: "100%", height: "48" }));
|
|
567
|
+
}
|
|
568
|
+
const label = (0, import_i18n8.__)("Pages (%s)", "elementor").replace("%s", pages.length.toString());
|
|
535
569
|
return /* @__PURE__ */ React17.createElement(
|
|
536
570
|
CollapsibleList,
|
|
537
571
|
{
|
|
@@ -544,36 +578,35 @@ function PagesCollapsibleList({ pages, isOpenByDefault = false }) {
|
|
|
544
578
|
}
|
|
545
579
|
|
|
546
580
|
// src/components/panel/shell.tsx
|
|
547
|
-
var
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
581
|
+
var import_i18n9 = require("@wordpress/i18n");
|
|
582
|
+
|
|
583
|
+
// src/components/panel/add-new-page-button.tsx
|
|
584
|
+
var import_ui13 = require("@elementor/ui");
|
|
585
|
+
var import_icons13 = require("@elementor/icons");
|
|
586
|
+
var React18 = __toESM(require("react"));
|
|
587
|
+
function AddNewPageButton() {
|
|
588
|
+
return /* @__PURE__ */ React18.createElement(
|
|
589
|
+
import_ui13.Button,
|
|
590
|
+
{
|
|
591
|
+
sx: { mt: 4, mb: 4, mr: 5 },
|
|
592
|
+
startIcon: /* @__PURE__ */ React18.createElement(import_icons13.PlusIcon, null),
|
|
593
|
+
onClick: () => null
|
|
594
|
+
},
|
|
595
|
+
"Add New"
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/components/panel/shell.tsx
|
|
560
600
|
var Shell = () => {
|
|
561
|
-
return /* @__PURE__ */
|
|
562
|
-
|
|
601
|
+
return /* @__PURE__ */ React19.createElement(import_editor_panels.Panel, null, /* @__PURE__ */ React19.createElement(import_editor_panels.PanelHeader, null, /* @__PURE__ */ React19.createElement(import_editor_panels.PanelHeaderTitle, null, (0, import_i18n9.__)("Pages", "elementor"))), /* @__PURE__ */ React19.createElement(import_editor_panels.PanelBody, null, /* @__PURE__ */ React19.createElement(
|
|
602
|
+
import_ui14.Box,
|
|
563
603
|
{
|
|
564
604
|
display: "flex",
|
|
565
605
|
justifyContent: "flex-end",
|
|
566
606
|
alignItems: "center"
|
|
567
607
|
},
|
|
568
|
-
/* @__PURE__ */
|
|
569
|
-
|
|
570
|
-
{
|
|
571
|
-
sx: { mt: 4, mb: 4, mr: 5 },
|
|
572
|
-
startIcon: /* @__PURE__ */ React18.createElement(import_icons13.PlusIcon, null)
|
|
573
|
-
},
|
|
574
|
-
"Add New"
|
|
575
|
-
)
|
|
576
|
-
), /* @__PURE__ */ React18.createElement(import_ui12.List, { dense: true }, /* @__PURE__ */ React18.createElement(PagesCollapsibleList, { pages: mockPages, isOpenByDefault: true }))));
|
|
608
|
+
/* @__PURE__ */ React19.createElement(AddNewPageButton, null)
|
|
609
|
+
), /* @__PURE__ */ React19.createElement(import_ui14.List, { dense: true }, /* @__PURE__ */ React19.createElement(PagesCollapsibleList, { isOpenByDefault: true }))));
|
|
577
610
|
};
|
|
578
611
|
var shell_default = Shell;
|
|
579
612
|
|
|
@@ -592,7 +625,7 @@ function useToggleButtonProps() {
|
|
|
592
625
|
const { isOpen, isBlocked } = usePanelStatus();
|
|
593
626
|
const { open, close } = usePanelActions();
|
|
594
627
|
return {
|
|
595
|
-
title: (0,
|
|
628
|
+
title: (0, import_i18n10.__)("Pages", "elementor"),
|
|
596
629
|
icon: import_icons14.PagesIcon,
|
|
597
630
|
onClick: () => isOpen ? close() : open(),
|
|
598
631
|
selected: isOpen,
|
package/dist/index.mjs
CHANGED
|
@@ -232,16 +232,15 @@ function RecentlyEdited() {
|
|
|
232
232
|
import { injectIntoPageIndication, toolsMenu } from "@elementor/editor-app-bar";
|
|
233
233
|
|
|
234
234
|
// src/hooks/useToggleButtonProps.ts
|
|
235
|
-
import { __ as
|
|
235
|
+
import { __ as __10 } from "@wordpress/i18n";
|
|
236
236
|
import { PagesIcon } from "@elementor/icons";
|
|
237
237
|
|
|
238
238
|
// src/components/panel/panel.ts
|
|
239
239
|
import { createPanel } from "@elementor/editor-panels";
|
|
240
240
|
|
|
241
241
|
// src/components/panel/shell.tsx
|
|
242
|
-
import * as
|
|
243
|
-
import { Box as
|
|
244
|
-
import { PlusIcon as PlusIcon2 } from "@elementor/icons";
|
|
242
|
+
import * as React19 from "react";
|
|
243
|
+
import { Box as Box4, List as List2 } from "@elementor/ui";
|
|
245
244
|
import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from "@elementor/editor-panels";
|
|
246
245
|
|
|
247
246
|
// src/components/panel/pages-list/pages-collapsible-list.tsx
|
|
@@ -288,6 +287,7 @@ function CollapsibleList({
|
|
|
288
287
|
|
|
289
288
|
// src/components/panel/pages-list/pages-collapsible-list.tsx
|
|
290
289
|
import { PageTypeIcon as PageTypeIcon2 } from "@elementor/icons";
|
|
290
|
+
import { Skeleton, Box as Box3 } from "@elementor/ui";
|
|
291
291
|
|
|
292
292
|
// src/components/panel/pages-list/page-list-item.tsx
|
|
293
293
|
import * as React16 from "react";
|
|
@@ -344,7 +344,7 @@ var PageTitle = ({ title }) => {
|
|
|
344
344
|
);
|
|
345
345
|
};
|
|
346
346
|
function PageTitleAndStatus({ page }) {
|
|
347
|
-
return /* @__PURE__ */ React7.createElement(Box2, { display: "flex" }, /* @__PURE__ */ React7.createElement(PageTitle, { title: page.title }), "\xA0", /* @__PURE__ */ React7.createElement(PageStatus, { status: page.status }));
|
|
347
|
+
return /* @__PURE__ */ React7.createElement(Box2, { display: "flex" }, /* @__PURE__ */ React7.createElement(PageTitle, { title: page.title.rendered }), "\xA0", /* @__PURE__ */ React7.createElement(PageStatus, { status: page.status }));
|
|
348
348
|
}
|
|
349
349
|
|
|
350
350
|
// src/components/panel/actions-menu/page-actions-menu.tsx
|
|
@@ -520,9 +520,43 @@ function PageListItem({ page }) {
|
|
|
520
520
|
), /* @__PURE__ */ React16.createElement(PageActionsMenu, { page, ...bindMenu2(popupState) }));
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
// src/hooks/use-posts.ts
|
|
524
|
+
import { useQuery } from "@elementor/query";
|
|
525
|
+
|
|
526
|
+
// src/api/post.ts
|
|
527
|
+
import apiFetch3 from "@wordpress/api-fetch";
|
|
528
|
+
var postTypesMap = {
|
|
529
|
+
page: "pages"
|
|
530
|
+
};
|
|
531
|
+
var getRequest = (postTypeSlug) => {
|
|
532
|
+
const baseUri = `/wp/v2/${postTypesMap[postTypeSlug]}`;
|
|
533
|
+
const keys = ["id", "type", "title", "link", "status"];
|
|
534
|
+
const queryParams = new URLSearchParams({
|
|
535
|
+
status: "any",
|
|
536
|
+
per_page: "-1",
|
|
537
|
+
_fields: keys.join(",")
|
|
538
|
+
});
|
|
539
|
+
const uri = baseUri + "?" + queryParams.toString();
|
|
540
|
+
return apiFetch3({ path: uri });
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/hooks/use-posts.ts
|
|
544
|
+
var postsQueryKey = (postTypeSlug) => ["site-navigation", "posts", postTypeSlug];
|
|
545
|
+
function usePosts(postTypeSlug) {
|
|
546
|
+
return useQuery({
|
|
547
|
+
queryKey: postsQueryKey(postTypeSlug),
|
|
548
|
+
queryFn: () => getRequest(postTypeSlug)
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
523
552
|
// src/components/panel/pages-list/pages-collapsible-list.tsx
|
|
524
|
-
|
|
525
|
-
|
|
553
|
+
import { __ as __8 } from "@wordpress/i18n";
|
|
554
|
+
function PagesCollapsibleList({ isOpenByDefault = false }) {
|
|
555
|
+
const { data: pages, isLoading: pagesLoading } = usePosts("page");
|
|
556
|
+
if (!pages || pagesLoading) {
|
|
557
|
+
return /* @__PURE__ */ React17.createElement(Box3, { spacing: 4, sx: { px: 6 } }, /* @__PURE__ */ React17.createElement(Skeleton, { variant: "text", sx: { fontSize: "2rem" } }), /* @__PURE__ */ React17.createElement(Skeleton, { variant: "rounded", width: "100%", height: "48" }));
|
|
558
|
+
}
|
|
559
|
+
const label = __8("Pages (%s)", "elementor").replace("%s", pages.length.toString());
|
|
526
560
|
return /* @__PURE__ */ React17.createElement(
|
|
527
561
|
CollapsibleList,
|
|
528
562
|
{
|
|
@@ -535,36 +569,35 @@ function PagesCollapsibleList({ pages, isOpenByDefault = false }) {
|
|
|
535
569
|
}
|
|
536
570
|
|
|
537
571
|
// src/components/panel/shell.tsx
|
|
538
|
-
import { __ as
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
572
|
+
import { __ as __9 } from "@wordpress/i18n";
|
|
573
|
+
|
|
574
|
+
// src/components/panel/add-new-page-button.tsx
|
|
575
|
+
import { Button as Button2 } from "@elementor/ui";
|
|
576
|
+
import { PlusIcon as PlusIcon2 } from "@elementor/icons";
|
|
577
|
+
import * as React18 from "react";
|
|
578
|
+
function AddNewPageButton() {
|
|
579
|
+
return /* @__PURE__ */ React18.createElement(
|
|
580
|
+
Button2,
|
|
581
|
+
{
|
|
582
|
+
sx: { mt: 4, mb: 4, mr: 5 },
|
|
583
|
+
startIcon: /* @__PURE__ */ React18.createElement(PlusIcon2, null),
|
|
584
|
+
onClick: () => null
|
|
585
|
+
},
|
|
586
|
+
"Add New"
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/components/panel/shell.tsx
|
|
551
591
|
var Shell = () => {
|
|
552
|
-
return /* @__PURE__ */
|
|
553
|
-
|
|
592
|
+
return /* @__PURE__ */ React19.createElement(Panel, null, /* @__PURE__ */ React19.createElement(PanelHeader, null, /* @__PURE__ */ React19.createElement(PanelHeaderTitle, null, __9("Pages", "elementor"))), /* @__PURE__ */ React19.createElement(PanelBody, null, /* @__PURE__ */ React19.createElement(
|
|
593
|
+
Box4,
|
|
554
594
|
{
|
|
555
595
|
display: "flex",
|
|
556
596
|
justifyContent: "flex-end",
|
|
557
597
|
alignItems: "center"
|
|
558
598
|
},
|
|
559
|
-
/* @__PURE__ */
|
|
560
|
-
|
|
561
|
-
{
|
|
562
|
-
sx: { mt: 4, mb: 4, mr: 5 },
|
|
563
|
-
startIcon: /* @__PURE__ */ React18.createElement(PlusIcon2, null)
|
|
564
|
-
},
|
|
565
|
-
"Add New"
|
|
566
|
-
)
|
|
567
|
-
), /* @__PURE__ */ React18.createElement(List2, { dense: true }, /* @__PURE__ */ React18.createElement(PagesCollapsibleList, { pages: mockPages, isOpenByDefault: true }))));
|
|
599
|
+
/* @__PURE__ */ React19.createElement(AddNewPageButton, null)
|
|
600
|
+
), /* @__PURE__ */ React19.createElement(List2, { dense: true }, /* @__PURE__ */ React19.createElement(PagesCollapsibleList, { isOpenByDefault: true }))));
|
|
568
601
|
};
|
|
569
602
|
var shell_default = Shell;
|
|
570
603
|
|
|
@@ -583,7 +616,7 @@ function useToggleButtonProps() {
|
|
|
583
616
|
const { isOpen, isBlocked } = usePanelStatus();
|
|
584
617
|
const { open, close } = usePanelActions();
|
|
585
618
|
return {
|
|
586
|
-
title:
|
|
619
|
+
title: __10("Pages", "elementor"),
|
|
587
620
|
icon: PagesIcon,
|
|
588
621
|
onClick: () => isOpen ? close() : open(),
|
|
589
622
|
selected: isOpen,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-site-navigation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"@elementor/editor-panels": "^0.1.3",
|
|
38
38
|
"@elementor/env": "^0.2.0",
|
|
39
39
|
"@elementor/icons": "^0.6.1",
|
|
40
|
+
"@elementor/query": "^0.1.1",
|
|
40
41
|
"@elementor/ui": "^1.4.53",
|
|
41
42
|
"@wordpress/api-fetch": "^6.33.0",
|
|
42
43
|
"@wordpress/i18n": "^4.36.0",
|
|
@@ -48,5 +49,5 @@
|
|
|
48
49
|
"elementor": {
|
|
49
50
|
"type": "extension"
|
|
50
51
|
},
|
|
51
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "45f277affda7a63cefad6682069bf32ed7cc38ef"
|
|
52
53
|
}
|
package/src/api/post.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
2
|
+
import { Post } from '../types';
|
|
3
|
+
|
|
4
|
+
export type NewPost = {
|
|
5
|
+
title: string,
|
|
6
|
+
status: 'publish' | 'draft'
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type UpdatePost = {
|
|
10
|
+
id: number,
|
|
11
|
+
title?: string,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Slug = keyof typeof postTypesMap;
|
|
15
|
+
|
|
16
|
+
const postTypesMap = {
|
|
17
|
+
page: 'pages',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getRequest = ( postTypeSlug: Slug ) => {
|
|
21
|
+
const baseUri = `/wp/v2/${ postTypesMap[ postTypeSlug ] }`;
|
|
22
|
+
|
|
23
|
+
const keys: Array<keyof Post> = [ 'id', 'type', 'title', 'link', 'status' ];
|
|
24
|
+
|
|
25
|
+
const queryParams = new URLSearchParams( {
|
|
26
|
+
status: 'any',
|
|
27
|
+
per_page: '-1',
|
|
28
|
+
_fields: keys.join( ',' ),
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
const uri = baseUri + '?' + queryParams.toString();
|
|
32
|
+
|
|
33
|
+
return apiFetch<Post[]>( { path: uri } );
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const createRequest = ( postTypeSlug: Slug, newPost: NewPost ) => {
|
|
37
|
+
const path = `/wp/v2/${ postTypesMap[ postTypeSlug ] }`;
|
|
38
|
+
|
|
39
|
+
return apiFetch( {
|
|
40
|
+
path,
|
|
41
|
+
method: 'POST',
|
|
42
|
+
data: newPost,
|
|
43
|
+
} );
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const updateRequest = ( postTypeSlug: Slug, updatedPost: UpdatePost ) => {
|
|
47
|
+
const path = `/wp/v2/${ postTypesMap[ postTypeSlug ] }`;
|
|
48
|
+
const { id, ...data } = updatedPost;
|
|
49
|
+
|
|
50
|
+
return apiFetch( {
|
|
51
|
+
path: `${ path }/${ id }`,
|
|
52
|
+
method: 'POST',
|
|
53
|
+
data,
|
|
54
|
+
} );
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const deleteRequest = ( postTypeSlug: Slug, postId: number ) => {
|
|
58
|
+
const path = `/wp/v2/${ postTypesMap[ postTypeSlug ] }`;
|
|
59
|
+
|
|
60
|
+
return apiFetch( {
|
|
61
|
+
path: `${ path }/${ postId }`,
|
|
62
|
+
method: 'DELETE',
|
|
63
|
+
} );
|
|
64
|
+
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
3
|
import { ListItemButton, ListItemIcon, ListItemText } from '@elementor/ui';
|
|
4
|
-
import {
|
|
4
|
+
import { Post } from '../../../types';
|
|
5
5
|
|
|
6
6
|
export type Props = {
|
|
7
7
|
title: string;
|
|
8
8
|
icon: ComponentType;
|
|
9
9
|
disabled?: boolean;
|
|
10
|
-
onClick: ( page:
|
|
10
|
+
onClick: ( page: Post ) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export default function ActionListItem( { title, icon: Icon, disabled, onClick }: Props ) {
|
|
@@ -3,12 +3,12 @@ import { Divider, Menu, MenuProps } from '@elementor/ui';
|
|
|
3
3
|
import Rename from '../pages-actions/rename';
|
|
4
4
|
import Duplicate from '../pages-actions/duplicate';
|
|
5
5
|
import Delete from '../pages-actions/delete';
|
|
6
|
-
import {
|
|
6
|
+
import { Post } from '../../../types';
|
|
7
7
|
import View from '../pages-actions/view';
|
|
8
8
|
import SetHome from '../pages-actions/set-home';
|
|
9
9
|
|
|
10
10
|
type Props = MenuProps & {
|
|
11
|
-
page:
|
|
11
|
+
page: Post
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export default function PageActionsMenu( { page, ...props }: Props ) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Button } from '@elementor/ui';
|
|
2
|
+
import { PlusIcon } from '@elementor/icons';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
export default function AddNewPageButton() {
|
|
6
|
+
return (
|
|
7
|
+
<Button
|
|
8
|
+
sx={ { mt: 4, mb: 4, mr: 5 } }
|
|
9
|
+
startIcon={ <PlusIcon /> }
|
|
10
|
+
onClick={ () => null }
|
|
11
|
+
>
|
|
12
|
+
Add New
|
|
13
|
+
</Button>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { TrashIcon } from '@elementor/icons';
|
|
3
|
-
import {
|
|
3
|
+
import { Post } from '../../../types';
|
|
4
4
|
import { useActiveDocument } from '@elementor/editor-documents';
|
|
5
5
|
import { __ } from '@wordpress/i18n';
|
|
6
6
|
import ActionMenuItem from '../actions-menu/action-menu-item';
|
|
7
7
|
|
|
8
|
-
export default function Delete( { page }: { page:
|
|
8
|
+
export default function Delete( { page }: { page: Post } ) {
|
|
9
9
|
const activeDocument = useActiveDocument();
|
|
10
10
|
|
|
11
11
|
const isActive = activeDocument?.id === page.id;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { Post } from '../../../types';
|
|
3
3
|
import { HomeIcon } from '@elementor/icons';
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
import ActionMenuItem from '../actions-menu/action-menu-item';
|
|
6
6
|
|
|
7
|
-
export default function SetHome( { page }: { page:
|
|
7
|
+
export default function SetHome( { page }: { page: Post } ) {
|
|
8
8
|
return (
|
|
9
9
|
<ActionMenuItem
|
|
10
10
|
title={ __( 'Set as homepage', 'elementor' ) }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { act, render } from '@testing-library/react';
|
|
3
|
-
import {
|
|
3
|
+
import { Post } from '../../../../types';
|
|
4
4
|
import PageListItem from '../page-list-item';
|
|
5
5
|
import { useNavigateToDocument } from '@elementor/editor-documents';
|
|
6
6
|
|
|
@@ -14,33 +14,39 @@ describe( '@elementor/editor-site-navigation - PageListItem', () => {
|
|
|
14
14
|
jest.clearAllMocks();
|
|
15
15
|
} );
|
|
16
16
|
|
|
17
|
-
it( 'should render a published
|
|
17
|
+
it( 'should render a published page', () => {
|
|
18
18
|
// Arrange.
|
|
19
|
-
const page:
|
|
19
|
+
const page: Post = {
|
|
20
20
|
id: 1,
|
|
21
|
-
title:
|
|
21
|
+
title: {
|
|
22
|
+
rendered: 'Test Page',
|
|
23
|
+
},
|
|
22
24
|
status: 'publish',
|
|
23
25
|
type: 'page',
|
|
26
|
+
link: 'https://example.local/test-page',
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
// Act.
|
|
27
30
|
const { getByText, queryByText } = render( <PageListItem page={ page } /> );
|
|
28
31
|
|
|
29
32
|
// Assert.
|
|
30
|
-
const label = getByText( 'Test
|
|
33
|
+
const label = getByText( 'Test Page' );
|
|
31
34
|
const publishedLabel = queryByText( 'publish', { exact: false } );
|
|
32
35
|
|
|
33
36
|
expect( label ).toBeInTheDocument();
|
|
34
37
|
expect( publishedLabel ).toBeNull();
|
|
35
38
|
} );
|
|
36
39
|
|
|
37
|
-
it( 'should show the
|
|
40
|
+
it( 'should show the page status for non-published pages', () => {
|
|
38
41
|
// Arrange.
|
|
39
|
-
const page:
|
|
42
|
+
const page: Post = {
|
|
40
43
|
id: 1,
|
|
41
|
-
title:
|
|
44
|
+
title: {
|
|
45
|
+
rendered: 'Test Page',
|
|
46
|
+
},
|
|
42
47
|
status: 'draft',
|
|
43
48
|
type: 'page',
|
|
49
|
+
link: 'https://example.local/test-page',
|
|
44
50
|
};
|
|
45
51
|
|
|
46
52
|
// Act.
|
|
@@ -53,11 +59,14 @@ describe( '@elementor/editor-site-navigation - PageListItem', () => {
|
|
|
53
59
|
|
|
54
60
|
it( 'should render actions menu', () => {
|
|
55
61
|
// Arrange.
|
|
56
|
-
const page:
|
|
62
|
+
const page: Post = {
|
|
57
63
|
id: 1,
|
|
58
|
-
title:
|
|
59
|
-
|
|
64
|
+
title: {
|
|
65
|
+
rendered: 'Test Page',
|
|
66
|
+
},
|
|
67
|
+
status: 'publish',
|
|
60
68
|
type: 'page',
|
|
69
|
+
link: 'https://example.local/test-Page',
|
|
61
70
|
};
|
|
62
71
|
|
|
63
72
|
const actions = [ 'View Page', 'Rename', 'Duplicate', 'Delete', 'Set as homepage' ];
|
|
@@ -87,11 +96,14 @@ describe( '@elementor/editor-site-navigation - PageListItem', () => {
|
|
|
87
96
|
|
|
88
97
|
const id = 10;
|
|
89
98
|
|
|
90
|
-
const page:
|
|
99
|
+
const page: Post = {
|
|
91
100
|
id,
|
|
92
|
-
title:
|
|
93
|
-
|
|
101
|
+
title: {
|
|
102
|
+
rendered: 'Test Page',
|
|
103
|
+
},
|
|
104
|
+
status: 'publish',
|
|
94
105
|
type: 'page',
|
|
106
|
+
link: 'https://example.local/test-page',
|
|
95
107
|
};
|
|
96
108
|
|
|
97
109
|
// Act.
|
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
|
-
import { Page } from '../../../../types';
|
|
4
3
|
import PagesCollapsibleList from '../pages-collapsible-list';
|
|
5
4
|
|
|
6
|
-
jest.mock( '
|
|
5
|
+
jest.mock( '../../../../hooks/use-posts', () => ( {
|
|
6
|
+
__esModule: true,
|
|
7
|
+
usePosts: jest.fn( () => ( {
|
|
8
|
+
isLoading: false,
|
|
9
|
+
data: [
|
|
10
|
+
{ id: 1, type: 'page', title: { rendered: 'Home' }, status: 'draft', link: 'www.test.demo' },
|
|
11
|
+
{ id: 2, type: 'page', title: { rendered: 'About' }, status: 'publish', link: 'www.test.demo' },
|
|
12
|
+
{ id: 3, type: 'page', title: { rendered: 'Services' }, status: 'publish', link: 'www.test.demo', isHome: true },
|
|
13
|
+
],
|
|
14
|
+
} ) ),
|
|
15
|
+
} ) );
|
|
16
|
+
|
|
17
|
+
jest.mock( '@elementor/editor-documents', () => ( {
|
|
18
|
+
__esModule: true,
|
|
19
|
+
useActiveDocument: jest.fn( () => ( {
|
|
20
|
+
id: 2,
|
|
21
|
+
} ) ),
|
|
22
|
+
useNavigateToDocument: jest.fn(),
|
|
23
|
+
} ) );
|
|
7
24
|
|
|
8
25
|
describe( '@elementor/editor-site-navigation - PagesCollapsibleList', () => {
|
|
9
|
-
|
|
10
|
-
{ id: 1, type: 'page', title: 'Home', status: 'draft' },
|
|
11
|
-
{ id: 2, type: 'page', title: 'About', status: 'publish' },
|
|
12
|
-
{ id: 3, type: 'page', title: 'Services', status: 'publish', isHome: true },
|
|
13
|
-
{ id: 4, type: 'page', title: 'Contact', status: 'draft' },
|
|
14
|
-
{ id: 5, type: 'page', title: 'FAQ', status: 'publish' },
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
afterAll( () => {
|
|
26
|
+
afterEach( () => {
|
|
18
27
|
jest.clearAllMocks();
|
|
19
28
|
} );
|
|
20
29
|
|
|
21
30
|
it( 'should render closed list', () => {
|
|
22
31
|
// Act.
|
|
23
32
|
const { getByText, queryByText } = render(
|
|
24
|
-
<PagesCollapsibleList
|
|
33
|
+
<PagesCollapsibleList isOpenByDefault={ false } />,
|
|
25
34
|
);
|
|
26
35
|
|
|
27
36
|
// Assert.
|
|
28
|
-
const label = getByText( `Pages (
|
|
37
|
+
const label = getByText( `Pages (3)` );
|
|
29
38
|
expect( label ).toBeInTheDocument();
|
|
30
39
|
|
|
31
40
|
const postInList = queryByText( 'Services' );
|
|
@@ -35,11 +44,11 @@ describe( '@elementor/editor-site-navigation - PagesCollapsibleList', () => {
|
|
|
35
44
|
it( 'should render open list', () => {
|
|
36
45
|
// Act.
|
|
37
46
|
const { getByText } = render(
|
|
38
|
-
<PagesCollapsibleList
|
|
47
|
+
<PagesCollapsibleList isOpenByDefault={ true } />,
|
|
39
48
|
);
|
|
40
49
|
|
|
41
50
|
// Assert.
|
|
42
|
-
const label = getByText( `Pages (
|
|
51
|
+
const label = getByText( `Pages (3)` );
|
|
43
52
|
expect( label ).toBeInTheDocument();
|
|
44
53
|
|
|
45
54
|
const postInList = getByText( 'Services' );
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
usePopupState,
|
|
11
11
|
} from '@elementor/ui';
|
|
12
12
|
import { DotsVerticalIcon, HomeIcon } from '@elementor/icons';
|
|
13
|
-
import {
|
|
13
|
+
import { Post } from '../../../types';
|
|
14
14
|
import { useActiveDocument, useNavigateToDocument } from '@elementor/editor-documents';
|
|
15
15
|
import PageTitleAndStatus from '../../shared/page-title-and-status';
|
|
16
16
|
import PageActionsMenu from '../actions-menu/page-actions-menu';
|
|
17
17
|
|
|
18
|
-
export default function PageListItem( { page }: { page:
|
|
18
|
+
export default function PageListItem( { page }: { page: Post } ) {
|
|
19
19
|
const popupState = usePopupState( {
|
|
20
20
|
variant: 'popover',
|
|
21
21
|
popupId: 'page-actions',
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import CollapsibleList from './collapsible-list';
|
|
3
|
-
import { Page } from '../../../types';
|
|
4
3
|
import { PageTypeIcon } from '@elementor/icons';
|
|
4
|
+
import { Skeleton, Box } from '@elementor/ui';
|
|
5
5
|
import PageListItem from './page-list-item';
|
|
6
|
+
import { usePosts } from '../../../hooks/use-posts';
|
|
7
|
+
import { __ } from '@wordpress/i18n';
|
|
6
8
|
|
|
7
9
|
type Props = {
|
|
8
|
-
pages: Page[];
|
|
9
10
|
isOpenByDefault?: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export default function PagesCollapsibleList( {
|
|
13
|
-
const
|
|
13
|
+
export default function PagesCollapsibleList( { isOpenByDefault = false }: Props ) {
|
|
14
|
+
const { data: pages, isLoading: pagesLoading } = usePosts( 'page' );
|
|
15
|
+
|
|
16
|
+
if ( ! pages || pagesLoading ) {
|
|
17
|
+
return (
|
|
18
|
+
<Box spacing={ 4 } sx={ { px: 6 } }>
|
|
19
|
+
<Skeleton variant="text" sx={ { fontSize: '2rem' } } />
|
|
20
|
+
<Skeleton variant="rounded" width="100%" height="48" />
|
|
21
|
+
</Box>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// translators: %s: Number of pages
|
|
26
|
+
const label = __( 'Pages (%s)', 'elementor' ).replace( '%s', ( pages.length ).toString() );
|
|
14
27
|
|
|
15
28
|
return (
|
|
16
29
|
<CollapsibleList
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { Box,
|
|
3
|
-
import { PlusIcon } from '@elementor/icons';
|
|
4
|
-
import { Page } from '../../types';
|
|
2
|
+
import { Box, List } from '@elementor/ui';
|
|
5
3
|
import { Panel, PanelBody, PanelHeader, PanelHeaderTitle } from '@elementor/editor-panels';
|
|
6
4
|
import PagesCollapsibleList from './pages-list/pages-collapsible-list';
|
|
7
5
|
import { __ } from '@wordpress/i18n';
|
|
8
|
-
|
|
9
|
-
// TODO: Remove once connected to real data fetching.
|
|
10
|
-
const mockPages: Page[] = [
|
|
11
|
-
{
|
|
12
|
-
id: 1,
|
|
13
|
-
type: 'page',
|
|
14
|
-
title: 'This is a very long title that somebody wrote, a very very long line',
|
|
15
|
-
status: 'pending approval',
|
|
16
|
-
},
|
|
17
|
-
{ id: 2, type: 'page', title: 'About', status: 'publish' },
|
|
18
|
-
{ id: 3, type: 'page', title: 'Services', status: 'publish', isHome: true },
|
|
19
|
-
{ id: 4, type: 'page', title: 'Contact', status: 'draft' },
|
|
20
|
-
{ id: 5, type: 'page', title: 'FAQ', status: 'publish' },
|
|
21
|
-
];
|
|
6
|
+
import AddNewPageButton from './add-new-page-button';
|
|
22
7
|
|
|
23
8
|
const Shell = () => {
|
|
24
9
|
return (
|
|
@@ -32,15 +17,10 @@ const Shell = () => {
|
|
|
32
17
|
justifyContent="flex-end"
|
|
33
18
|
alignItems="center"
|
|
34
19
|
>
|
|
35
|
-
<
|
|
36
|
-
sx={ { mt: 4, mb: 4, mr: 5 } }
|
|
37
|
-
startIcon={ <PlusIcon /> }
|
|
38
|
-
>
|
|
39
|
-
Add New
|
|
40
|
-
</Button>
|
|
20
|
+
<AddNewPageButton />
|
|
41
21
|
</Box>
|
|
42
22
|
<List dense>
|
|
43
|
-
<PagesCollapsibleList
|
|
23
|
+
<PagesCollapsibleList isOpenByDefault={ true } />
|
|
44
24
|
</List>
|
|
45
25
|
</PanelBody>
|
|
46
26
|
</Panel>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Box, Typography } from '@elementor/ui';
|
|
3
|
-
import {
|
|
3
|
+
import { Post } from '../../types';
|
|
4
4
|
import { useReverseHtmlEntities } from '../../hooks/use-reverse-html-entities';
|
|
5
5
|
|
|
6
6
|
const PageStatus = ( { status }: { status: string } ) => {
|
|
@@ -41,10 +41,10 @@ const PageTitle = ( { title }: { title: string } ) => {
|
|
|
41
41
|
);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
export default function PageTitleAndStatus( { page }: { page:
|
|
44
|
+
export default function PageTitleAndStatus( { page }: { page: Post } ) {
|
|
45
45
|
return (
|
|
46
46
|
<Box display="flex">
|
|
47
|
-
<PageTitle title={ page.title } /> <PageStatus status={ page.status } />
|
|
47
|
+
<PageTitle title={ page.title.rendered } /> <PageStatus status={ page.status } />
|
|
48
48
|
</Box>
|
|
49
49
|
);
|
|
50
50
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
2
|
+
import { renderHookWithQuery } from 'test-utils';
|
|
3
|
+
import { usePostActions } from '../use-posts-actions';
|
|
4
|
+
import { postsQueryKey } from '../use-posts';
|
|
5
|
+
|
|
6
|
+
jest.mock( '@wordpress/api-fetch' );
|
|
7
|
+
|
|
8
|
+
describe( '@elementor/site-settings/use-post-actions', () => {
|
|
9
|
+
beforeEach( () => {
|
|
10
|
+
jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( {} ) );
|
|
11
|
+
} );
|
|
12
|
+
|
|
13
|
+
afterEach( () => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
} );
|
|
16
|
+
|
|
17
|
+
it( 'should run createPost from usePostActions hook', async () => {
|
|
18
|
+
// Arrange.
|
|
19
|
+
const { component, queryClient } = renderHookWithQuery( () => usePostActions( 'page' ) );
|
|
20
|
+
const { createPost } = component.result.current;
|
|
21
|
+
|
|
22
|
+
const queryKey = postsQueryKey( 'page' );
|
|
23
|
+
await queryClient.setQueryData( queryKey, {
|
|
24
|
+
posts: [],
|
|
25
|
+
} );
|
|
26
|
+
|
|
27
|
+
// Act.
|
|
28
|
+
await createPost.mutateAsync( { title: 'Page', status: 'publish' } );
|
|
29
|
+
|
|
30
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
31
|
+
expect( apiFetch ).toHaveBeenCalledWith( {
|
|
32
|
+
path: '/wp/v2/pages',
|
|
33
|
+
method: 'POST',
|
|
34
|
+
data: {
|
|
35
|
+
title: 'Page',
|
|
36
|
+
status: 'publish',
|
|
37
|
+
},
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
expect( queryClient.getQueryState( queryKey )?.isInvalidated ).toBe( true );
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
it( 'should run updatePost from usePostActions hook', async () => {
|
|
44
|
+
// Arrange.
|
|
45
|
+
const { component, queryClient } = renderHookWithQuery( () => usePostActions( 'page' ) );
|
|
46
|
+
const { updatePost } = component.result.current;
|
|
47
|
+
|
|
48
|
+
const queryKey = postsQueryKey( 'page' );
|
|
49
|
+
await queryClient.setQueryData( queryKey, {
|
|
50
|
+
posts: [],
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
// Act.
|
|
54
|
+
await updatePost.mutateAsync( { id: 1, title: 'Page' } );
|
|
55
|
+
|
|
56
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
57
|
+
expect( apiFetch ).toHaveBeenCalledWith( {
|
|
58
|
+
path: '/wp/v2/pages/1',
|
|
59
|
+
method: 'POST',
|
|
60
|
+
data: {
|
|
61
|
+
title: 'Page',
|
|
62
|
+
},
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
expect( queryClient.getQueryState( queryKey )?.isInvalidated ).toBe( true );
|
|
66
|
+
} );
|
|
67
|
+
|
|
68
|
+
it( 'should run deletePost from usePostActions hook', async () => {
|
|
69
|
+
// Arrange.
|
|
70
|
+
const { component, queryClient } = renderHookWithQuery( () => usePostActions( 'page' ) );
|
|
71
|
+
const { deletePost } = component.result.current;
|
|
72
|
+
|
|
73
|
+
const queryKey = postsQueryKey( 'page' );
|
|
74
|
+
await queryClient.setQueryData( queryKey, {
|
|
75
|
+
posts: [],
|
|
76
|
+
} );
|
|
77
|
+
|
|
78
|
+
// Act.
|
|
79
|
+
await deletePost.mutateAsync( 1 );
|
|
80
|
+
|
|
81
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
82
|
+
expect( apiFetch ).toHaveBeenCalledWith( {
|
|
83
|
+
path: '/wp/v2/pages/1',
|
|
84
|
+
method: 'DELETE',
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
expect( queryClient.getQueryState( queryKey )?.isInvalidated ).toBe( true );
|
|
88
|
+
} );
|
|
89
|
+
} );
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { waitFor } from '@testing-library/react';
|
|
2
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
3
|
+
import { renderHookWithQuery } from 'test-utils';
|
|
4
|
+
import { usePosts } from '../use-posts';
|
|
5
|
+
|
|
6
|
+
jest.mock( '@wordpress/api-fetch' );
|
|
7
|
+
|
|
8
|
+
describe( '@elementor/site-settings/use-posts', () => {
|
|
9
|
+
beforeEach( () => {
|
|
10
|
+
jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( [] ) );
|
|
11
|
+
} );
|
|
12
|
+
|
|
13
|
+
afterEach( () => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
} );
|
|
16
|
+
|
|
17
|
+
it( 'usePosts hook should return posts list by type', async () => {
|
|
18
|
+
// Arrange.
|
|
19
|
+
const pages = [
|
|
20
|
+
{ id: 1, type: 'page', title: { rendered: 'Home' }, status: 'draft', link: 'www.test.demo' },
|
|
21
|
+
{ id: 2, type: 'page', title: { rendered: 'About' }, status: 'publish', link: 'www.test.demo' },
|
|
22
|
+
{ id: 3, type: 'page', title: { rendered: 'Services' }, status: 'publish', link: 'www.test.demo', isHome: true },
|
|
23
|
+
];
|
|
24
|
+
jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( pages ) );
|
|
25
|
+
|
|
26
|
+
// Act.
|
|
27
|
+
const { component } = renderHookWithQuery( () => usePosts( 'page' ) );
|
|
28
|
+
|
|
29
|
+
// Assert.
|
|
30
|
+
const expectedPath = `/wp/v2/pages?status=any&per_page=-1&_fields=${ encodeURIComponent( 'id,type,title,link,status' ) }`;
|
|
31
|
+
|
|
32
|
+
await waitFor( () => {
|
|
33
|
+
expect( apiFetch ).toHaveBeenCalledWith( {
|
|
34
|
+
path: expectedPath,
|
|
35
|
+
} );
|
|
36
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
await waitFor( () => {
|
|
40
|
+
return component.result.current.isSuccess;
|
|
41
|
+
} );
|
|
42
|
+
|
|
43
|
+
expect( component.result.current.data ).toBe( pages );
|
|
44
|
+
} );
|
|
45
|
+
} );
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useQueryClient, useMutation } from '@elementor/query';
|
|
2
|
+
import { createRequest, deleteRequest, updateRequest, NewPost, Slug, UpdatePost } from '../api/post';
|
|
3
|
+
import { postsQueryKey } from './use-posts';
|
|
4
|
+
|
|
5
|
+
export function usePostActions( postTypeSlug: Slug ) {
|
|
6
|
+
const invalidatePosts = useInvalidatePosts( postTypeSlug );
|
|
7
|
+
|
|
8
|
+
const onSuccess = () => invalidatePosts( { exact: true } );
|
|
9
|
+
|
|
10
|
+
const createPost = useMutation(
|
|
11
|
+
( newPost: NewPost ) => createRequest( postTypeSlug, newPost ),
|
|
12
|
+
{ onSuccess }
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const updatePost = useMutation(
|
|
16
|
+
( updatedPost: UpdatePost ) => updateRequest( postTypeSlug, updatedPost ),
|
|
17
|
+
{ onSuccess }
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const deletePost = useMutation(
|
|
21
|
+
( postId: number ) => deleteRequest( postTypeSlug, postId ),
|
|
22
|
+
{ onSuccess }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
createPost,
|
|
27
|
+
updatePost,
|
|
28
|
+
deletePost,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function useInvalidatePosts( postTypeSlug: string ) {
|
|
33
|
+
const queryClient = useQueryClient();
|
|
34
|
+
|
|
35
|
+
return ( options = {} ) => {
|
|
36
|
+
const queryKey = postsQueryKey( postTypeSlug );
|
|
37
|
+
|
|
38
|
+
return queryClient.invalidateQueries( queryKey, options );
|
|
39
|
+
};
|
|
40
|
+
}
|
package/src/hooks/use-posts.ts
CHANGED
|
@@ -1,123 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Post, PostType } from './types/interfaces';
|
|
4
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
1
|
+
import { useQuery } from '@elementor/query';
|
|
2
|
+
import { getRequest, Slug } from '../api/post';
|
|
5
3
|
|
|
6
|
-
const
|
|
4
|
+
export const postsQueryKey = ( postTypeSlug: string ) => [ 'site-navigation', 'posts', postTypeSlug ];
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const path = postType
|
|
16
|
-
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
17
|
-
: `/${ defaultRestNamespace }/posts`;
|
|
18
|
-
|
|
19
|
-
apiFetch( { path } )
|
|
20
|
-
.then( ( response ) => {
|
|
21
|
-
if ( Array.isArray( response ) ) {
|
|
22
|
-
setPosts( response );
|
|
23
|
-
}
|
|
24
|
-
} )
|
|
25
|
-
.catch( ( error ) => {
|
|
26
|
-
// eslint-disable-next-line no-console
|
|
27
|
-
console.error( 'Error fetching posts:', error );
|
|
28
|
-
} )
|
|
29
|
-
.finally( () => {
|
|
30
|
-
setIsLoading( false );
|
|
31
|
-
} );
|
|
32
|
-
}, [ postType ] );
|
|
33
|
-
|
|
34
|
-
useEffect( () => {
|
|
35
|
-
fetchPosts();
|
|
36
|
-
}, [ fetchPosts ] );
|
|
37
|
-
|
|
38
|
-
return [ posts, fetchPosts, isLoading ];
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const useUpdatePost = ( postType: PostType | null | undefined ): ( ( postId: number, updatedPost: Partial<Post> ) => void ) => {
|
|
42
|
-
return ( postId: number, updatedPost: Partial<Post> ) => {
|
|
43
|
-
const path = postType
|
|
44
|
-
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
45
|
-
: `/${ defaultRestNamespace }/posts`;
|
|
46
|
-
|
|
47
|
-
apiFetch( {
|
|
48
|
-
path: `/${ path }/${ postId }`,
|
|
49
|
-
method: 'POST',
|
|
50
|
-
data: updatedPost,
|
|
51
|
-
} )
|
|
52
|
-
.catch( ( error ) => {
|
|
53
|
-
// eslint-disable-next-line no-console
|
|
54
|
-
console.error( 'Error updating post:', error );
|
|
55
|
-
} );
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const useCreatePost = ( postType: PostType | null | undefined ): ( ( newPost: Partial<Post> ) => void ) => {
|
|
60
|
-
return ( newPost: Partial<Post> ) => {
|
|
61
|
-
const path = postType
|
|
62
|
-
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
63
|
-
: `/${ defaultRestNamespace }/posts`;
|
|
64
|
-
|
|
65
|
-
apiFetch( {
|
|
66
|
-
path,
|
|
67
|
-
method: 'POST',
|
|
68
|
-
data: newPost,
|
|
69
|
-
} )
|
|
70
|
-
.catch( ( error ) => {
|
|
71
|
-
// eslint-disable-next-line no-console
|
|
72
|
-
console.error( 'Error creating post:', error );
|
|
73
|
-
} );
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const useDeletePost = ( postType: PostType | null | undefined ): ( ( postId: number ) => void ) => {
|
|
78
|
-
return ( postId: number ) => {
|
|
79
|
-
const path = postType
|
|
80
|
-
? `/${ postType.rest_namespace }/${ postType.rest_base }`
|
|
81
|
-
: `/${ defaultRestNamespace }/posts`;
|
|
82
|
-
|
|
83
|
-
apiFetch( {
|
|
84
|
-
path: `/${ path }/${ postId }`,
|
|
85
|
-
method: 'DELETE',
|
|
86
|
-
} )
|
|
87
|
-
.catch( ( error ) => {
|
|
88
|
-
// eslint-disable-next-line no-console
|
|
89
|
-
console.error( 'Error deleting post:', error );
|
|
90
|
-
} );
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export const usePosts = ( postType?: string | null ): {
|
|
95
|
-
posts: Post[];
|
|
96
|
-
getPosts: () => void;
|
|
97
|
-
updatePost: ( postId: number, updatedPost: Partial<Post> ) => void;
|
|
98
|
-
createPost: ( newPost: Partial<Post> ) => void;
|
|
99
|
-
deletePost: ( postId: number ) => void;
|
|
100
|
-
isLoading: boolean;
|
|
101
|
-
} => {
|
|
102
|
-
const postTypes = usePostTypes();
|
|
103
|
-
const postTypeData = postTypes.find( ( type ) => type?.name === postType );
|
|
104
|
-
|
|
105
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
106
|
-
const [ posts, fetchPosts, isLoading ] = useFetchPosts( postTypeData );
|
|
107
|
-
const updatePost = useUpdatePost( postTypeData );
|
|
108
|
-
const createPost = useCreatePost( postTypeData );
|
|
109
|
-
const deletePost = useDeletePost( postTypeData );
|
|
110
|
-
|
|
111
|
-
const getPosts = useMemo( () => {
|
|
112
|
-
return () => posts;
|
|
113
|
-
}, [ posts ] );
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
posts,
|
|
117
|
-
getPosts,
|
|
118
|
-
updatePost,
|
|
119
|
-
createPost,
|
|
120
|
-
deletePost,
|
|
121
|
-
isLoading,
|
|
122
|
-
};
|
|
123
|
-
};
|
|
6
|
+
export function usePosts( postTypeSlug: Slug ) {
|
|
7
|
+
return useQuery( {
|
|
8
|
+
queryKey: postsQueryKey( postTypeSlug ),
|
|
9
|
+
queryFn: () => getRequest( postTypeSlug ),
|
|
10
|
+
} );
|
|
11
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type Post = {
|
|
2
2
|
isHome?: boolean;
|
|
3
3
|
id: number;
|
|
4
|
-
|
|
4
|
+
link: string;
|
|
5
5
|
status: string;
|
|
6
6
|
type: string;
|
|
7
|
-
|
|
7
|
+
title: {
|
|
8
|
+
rendered: string;
|
|
9
|
+
}
|
|
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;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
-
import { PostType } from './types/interfaces';
|
|
3
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
4
|
-
|
|
5
|
-
type PostTypesResponse = Record<string, PostType>;
|
|
6
|
-
|
|
7
|
-
// allowedPostTypes is only used to filter irrelevant types when fetching all post types.
|
|
8
|
-
// You can still use a specific post type name to fetch a post type not present in allowedPostTypes.
|
|
9
|
-
const allowedPostTypes = [ 'page' ];
|
|
10
|
-
|
|
11
|
-
const useFetchPostTypes = (
|
|
12
|
-
postTypeName?: string
|
|
13
|
-
): ( PostType | null )[] => {
|
|
14
|
-
const [ postTypes, setPostTypes ] = useState<( PostType | null )[]>( [] );
|
|
15
|
-
|
|
16
|
-
useEffect( () => {
|
|
17
|
-
const fetchPostTypes = async () => {
|
|
18
|
-
try {
|
|
19
|
-
const response: PostTypesResponse = await apiFetch( {
|
|
20
|
-
path: '/wp/v2/types',
|
|
21
|
-
} );
|
|
22
|
-
|
|
23
|
-
if ( postTypeName ) {
|
|
24
|
-
const specificPostType = response[ postTypeName ];
|
|
25
|
-
|
|
26
|
-
setPostTypes( [
|
|
27
|
-
specificPostType
|
|
28
|
-
? { ...specificPostType }
|
|
29
|
-
: null,
|
|
30
|
-
] );
|
|
31
|
-
} else {
|
|
32
|
-
const filteredResponse: PostTypesResponse = {};
|
|
33
|
-
|
|
34
|
-
allowedPostTypes.forEach( ( type ) => {
|
|
35
|
-
if ( response[ type ] ) {
|
|
36
|
-
filteredResponse[ type ] = response[ type ];
|
|
37
|
-
}
|
|
38
|
-
} );
|
|
39
|
-
|
|
40
|
-
const filteredPostTypes: ( PostType | null )[] = Object.keys( filteredResponse ).map(
|
|
41
|
-
( key ) => ( { ...filteredResponse[ key ] } )
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
setPostTypes( filteredPostTypes );
|
|
45
|
-
}
|
|
46
|
-
} catch ( error ) {
|
|
47
|
-
// eslint-disable-next-line no-console
|
|
48
|
-
console.error( 'Error fetching post types:', error );
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
fetchPostTypes();
|
|
53
|
-
}, [ postTypeName ] );
|
|
54
|
-
|
|
55
|
-
return postTypes;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const usePostTypes = (
|
|
59
|
-
postTypeName?: string
|
|
60
|
-
): ( PostType | null )[] => {
|
|
61
|
-
const postTypes = useFetchPostTypes( postTypeName );
|
|
62
|
-
|
|
63
|
-
return useMemo( () => postTypes, [ postTypes ] );
|
|
64
|
-
};
|