@elementor/editor-site-navigation 0.9.2 → 0.10.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.10.1](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.10.0...@elementor/editor-site-navigation@0.10.1) (2023-07-16)
7
+
8
+ **Note:** Version bump only for package @elementor/editor-site-navigation
9
+
10
+
11
+
12
+
13
+
14
+ # [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)
15
+
16
+
17
+ ### Features
18
+
19
+ * **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))
20
+
21
+
22
+
23
+
24
+
6
25
  ## [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
26
 
8
27
  **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 import_i18n9 = require("@wordpress/i18n");
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 React18 = __toESM(require("react"));
261
- var import_ui12 = require("@elementor/ui");
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
- function PagesCollapsibleList({ pages, isOpenByDefault = false }) {
534
- const label = `Pages (${pages.length})`;
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 import_i18n8 = require("@wordpress/i18n");
548
- var mockPages = [
549
- {
550
- id: 1,
551
- type: "page",
552
- title: "This is a very long title that somebody wrote, a very very long line",
553
- status: "pending approval"
554
- },
555
- { id: 2, type: "page", title: "About", status: "publish" },
556
- { id: 3, type: "page", title: "Services", status: "publish", isHome: true },
557
- { id: 4, type: "page", title: "Contact", status: "draft" },
558
- { id: 5, type: "page", title: "FAQ", status: "publish" }
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__ */ React18.createElement(import_editor_panels.Panel, null, /* @__PURE__ */ React18.createElement(import_editor_panels.PanelHeader, null, /* @__PURE__ */ React18.createElement(import_editor_panels.PanelHeaderTitle, null, (0, import_i18n8.__)("Pages", "elementor"))), /* @__PURE__ */ React18.createElement(import_editor_panels.PanelBody, null, /* @__PURE__ */ React18.createElement(
562
- import_ui12.Box,
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__ */ React18.createElement(
569
- import_ui12.Button,
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, import_i18n9.__)("Pages", "elementor"),
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 __9 } from "@wordpress/i18n";
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 React18 from "react";
243
- import { Box as Box3, Button as Button2, List as List2 } from "@elementor/ui";
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
- function PagesCollapsibleList({ pages, isOpenByDefault = false }) {
525
- const label = `Pages (${pages.length})`;
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 __8 } from "@wordpress/i18n";
539
- var mockPages = [
540
- {
541
- id: 1,
542
- type: "page",
543
- title: "This is a very long title that somebody wrote, a very very long line",
544
- status: "pending approval"
545
- },
546
- { id: 2, type: "page", title: "About", status: "publish" },
547
- { id: 3, type: "page", title: "Services", status: "publish", isHome: true },
548
- { id: 4, type: "page", title: "Contact", status: "draft" },
549
- { id: 5, type: "page", title: "FAQ", status: "publish" }
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__ */ React18.createElement(Panel, null, /* @__PURE__ */ React18.createElement(PanelHeader, null, /* @__PURE__ */ React18.createElement(PanelHeaderTitle, null, __8("Pages", "elementor"))), /* @__PURE__ */ React18.createElement(PanelBody, null, /* @__PURE__ */ React18.createElement(
553
- Box3,
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__ */ React18.createElement(
560
- Button2,
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: __9("Pages", "elementor"),
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.9.2",
3
+ "version": "0.10.1",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -32,11 +32,12 @@
32
32
  "dev": "tsup --config=../../tsup.dev.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@elementor/editor-app-bar": "^0.6.6",
35
+ "@elementor/editor-app-bar": "^0.6.7",
36
36
  "@elementor/editor-documents": "^0.8.3",
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": "acc2a7452306ed37a02647b977841c387bd1a7f5"
52
+ "gitHead": "c1914f9cf8125926aeef104768bea639fba19296"
52
53
  }
@@ -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 { Page } from '../../../types';
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: Page ) => void;
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 { Page } from '../../../types';
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: 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 { Page } from '../../../types';
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: 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 { Page } from '../../../types';
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: 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 { Page } from '../../../../types';
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 post', () => {
17
+ it( 'should render a published page', () => {
18
18
  // Arrange.
19
- const page: Page = {
19
+ const page: Post = {
20
20
  id: 1,
21
- title: 'Test Post',
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 Post' );
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 post status for non-published posts', () => {
40
+ it( 'should show the page status for non-published pages', () => {
38
41
  // Arrange.
39
- const page: Page = {
42
+ const page: Post = {
40
43
  id: 1,
41
- title: 'Test Post',
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: Page = {
62
+ const page: Post = {
57
63
  id: 1,
58
- title: 'Test Post',
59
- status: 'draft',
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: Page = {
99
+ const page: Post = {
91
100
  id,
92
- title: 'Test Post',
93
- status: 'draft',
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( '@elementor/editor-documents' );
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
- const mockPosts: Page[] = [
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 pages={ mockPosts } isOpenByDefault={ false } />,
33
+ <PagesCollapsibleList isOpenByDefault={ false } />,
25
34
  );
26
35
 
27
36
  // Assert.
28
- const label = getByText( `Pages (${ mockPosts.length })` );
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 pages={ mockPosts } isOpenByDefault={ true } />,
47
+ <PagesCollapsibleList isOpenByDefault={ true } />,
39
48
  );
40
49
 
41
50
  // Assert.
42
- const label = getByText( `Pages (${ mockPosts.length })` );
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 { Page } from '../../../types';
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: 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( { pages, isOpenByDefault = false }: Props ) {
13
- const label = `Pages (${ pages.length })`; // TODO 21/06/2023 : This label should come from the backend
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, Button, List } from '@elementor/ui';
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
- <Button
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 pages={ mockPages } isOpenByDefault={ true } />
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 { Page } from '../../types';
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: Page } ) {
44
+ export default function PageTitleAndStatus( { page }: { page: Post } ) {
45
45
  return (
46
46
  <Box display="flex">
47
- <PageTitle title={ page.title } />&nbsp;<PageStatus status={ page.status } />
47
+ <PageTitle title={ page.title.rendered } />&nbsp;<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
+ }
@@ -1,123 +1,11 @@
1
- import { useEffect, useMemo, useState, useCallback } from 'react';
2
- import { usePostTypes } from './use-post-types';
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 defaultRestNamespace = 'wp/v2';
4
+ export const postsQueryKey = ( postTypeSlug: string ) => [ 'site-navigation', 'posts', postTypeSlug ];
7
5
 
8
- const useFetchPosts = ( postType: PostType | null | undefined ): [ Post[], () => void, boolean ] => {
9
- const [ posts, setPosts ] = useState<Post[]>( [] );
10
- const [ isLoading, setIsLoading ] = useState( false );
11
-
12
- const fetchPosts = useCallback( () => {
13
- setIsLoading( true );
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 Page = {
1
+ export type Post = {
2
2
  isHome?: boolean;
3
3
  id: number;
4
- title: string;
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,13 +0,0 @@
1
- export interface Post {
2
- id: number;
3
- title: string;
4
- content: string;
5
- }
6
-
7
- export interface PostType {
8
- name: string;
9
- slug: string;
10
- labels: Record<string, string>;
11
- rest_base: string;
12
- rest_namespace: string;
13
- }
@@ -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
- };