@elementor/editor-site-navigation 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/index.js +39 -9
- package/dist/index.mjs +43 -13
- package/package.json +5 -5
- package/src/api/post.ts +31 -3
- package/src/components/panel/posts-list/__tests__/posts-collapsible-list.test.tsx +46 -4
- package/src/components/panel/posts-list/collapsible-list.tsx +1 -1
- package/src/components/panel/posts-list/posts-collapsible-list.tsx +13 -3
- package/src/hooks/__tests__/use-posts.test.ts +44 -10
- package/src/hooks/use-posts.ts +25 -4
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.21.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.20.0...@elementor/editor-site-navigation@0.21.0) (2024-01-29)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **editor-site-navigation:** pagination for pages panel ([#154](https://github.com/elementor/elementor-packages/issues/154)) ([1a1e1f4](https://github.com/elementor/elementor-packages/commit/1a1e1f46006988d10e0a7af292dbbc57644ed16f))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# [0.20.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.19.11...@elementor/editor-site-navigation@0.20.0) (2024-01-07)
|
|
7
18
|
|
|
8
19
|
|
package/dist/index.js
CHANGED
|
@@ -358,17 +358,28 @@ var postTypesMap = {
|
|
|
358
358
|
rest_base: "pages"
|
|
359
359
|
}
|
|
360
360
|
};
|
|
361
|
-
var
|
|
361
|
+
var POST_PER_PAGE = 10;
|
|
362
|
+
var getRequest2 = async (postTypeSlug, page) => {
|
|
362
363
|
const baseUri = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
|
|
363
364
|
const keys = ["id", "type", "title", "link", "status", "user_can"];
|
|
364
365
|
const queryParams = new URLSearchParams({
|
|
365
366
|
status: "any",
|
|
366
|
-
per_page: "-1",
|
|
367
367
|
order: "asc",
|
|
368
|
+
page: page.toString(),
|
|
369
|
+
per_page: POST_PER_PAGE.toString(),
|
|
368
370
|
_fields: keys.join(",")
|
|
369
371
|
});
|
|
370
372
|
const uri = baseUri + "?" + queryParams.toString();
|
|
371
|
-
|
|
373
|
+
const result = await (0, import_api_fetch4.default)({ path: uri, parse: false });
|
|
374
|
+
const data = await result.json();
|
|
375
|
+
const totalPages = Number(result.headers.get("x-wp-totalpages"));
|
|
376
|
+
const totalPosts = Number(result.headers.get("x-wp-total"));
|
|
377
|
+
return {
|
|
378
|
+
data,
|
|
379
|
+
totalPages,
|
|
380
|
+
totalPosts,
|
|
381
|
+
currentPage: page
|
|
382
|
+
};
|
|
372
383
|
};
|
|
373
384
|
var createRequest = (postTypeSlug, newPost) => {
|
|
374
385
|
const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
|
|
@@ -408,11 +419,26 @@ var duplicateRequest = (originalPost) => {
|
|
|
408
419
|
|
|
409
420
|
// src/hooks/use-posts.ts
|
|
410
421
|
var postsQueryKey = (postTypeSlug) => ["site-navigation", "posts", postTypeSlug];
|
|
422
|
+
var flattenData = (data) => {
|
|
423
|
+
if (!data) {
|
|
424
|
+
return data;
|
|
425
|
+
}
|
|
426
|
+
const flattened = [];
|
|
427
|
+
data.pages.forEach((page) => {
|
|
428
|
+
flattened.push(...page.data);
|
|
429
|
+
});
|
|
430
|
+
return flattened;
|
|
431
|
+
};
|
|
411
432
|
function usePosts(postTypeSlug) {
|
|
412
|
-
|
|
433
|
+
const query = (0, import_query3.useInfiniteQuery)({
|
|
413
434
|
queryKey: postsQueryKey(postTypeSlug),
|
|
414
|
-
queryFn: () => getRequest2(postTypeSlug)
|
|
435
|
+
queryFn: ({ pageParam = 1 }) => getRequest2(postTypeSlug, pageParam),
|
|
436
|
+
initialPageParam: 1,
|
|
437
|
+
getNextPageParam: (lastPage) => {
|
|
438
|
+
return lastPage.currentPage < lastPage.totalPages ? lastPage.currentPage + 1 : void 0;
|
|
439
|
+
}
|
|
415
440
|
});
|
|
441
|
+
return { ...query, data: { posts: flattenData(query.data), total: query.data?.pages[0]?.totalPosts ?? 0 } };
|
|
416
442
|
}
|
|
417
443
|
|
|
418
444
|
// src/contexts/post-list-context.tsx
|
|
@@ -503,7 +529,7 @@ function CollapsibleList({
|
|
|
503
529
|
unmountOnExit: true
|
|
504
530
|
},
|
|
505
531
|
/* @__PURE__ */ React7.createElement(import_ui6.List, { dense: true }, children)
|
|
506
|
-
), /* @__PURE__ */ React7.createElement(import_ui6.Divider, { sx: {
|
|
532
|
+
), /* @__PURE__ */ React7.createElement(import_ui6.Divider, { sx: { mt: 1 } }));
|
|
507
533
|
}
|
|
508
534
|
|
|
509
535
|
// src/components/panel/posts-list/post-list-item.tsx
|
|
@@ -1157,7 +1183,7 @@ function ErrorState() {
|
|
|
1157
1183
|
// src/components/panel/posts-list/posts-collapsible-list.tsx
|
|
1158
1184
|
function PostsCollapsibleList({ isOpenByDefault = false }) {
|
|
1159
1185
|
const { type, editMode } = usePostListContext();
|
|
1160
|
-
const { data: posts, isLoading: postsLoading, isError: postsError } = usePosts(type);
|
|
1186
|
+
const { data: { posts, total }, isLoading: postsLoading, isError: postsError, fetchNextPage, hasNextPage, isFetchingNextPage } = usePosts(type);
|
|
1161
1187
|
const { data: homepageId } = useHomepage();
|
|
1162
1188
|
if (postsError) {
|
|
1163
1189
|
return /* @__PURE__ */ React23.createElement(ErrorState, null);
|
|
@@ -1173,7 +1199,7 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
|
|
|
1173
1199
|
/* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 4 }, animation: "wave", variant: "rounded", width: "110px", height: "28px" })
|
|
1174
1200
|
), /* @__PURE__ */ React23.createElement(import_ui15.Box, null, /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "100%", height: "24px" }), /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(import_ui15.Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" })));
|
|
1175
1201
|
}
|
|
1176
|
-
const label = `${postTypesMap[type].labels.plural_name} (${
|
|
1202
|
+
const label = `${postTypesMap[type].labels.plural_name} (${total.toString()})`;
|
|
1177
1203
|
const mappedPosts = posts.map((post) => {
|
|
1178
1204
|
if (post.id === homepageId) {
|
|
1179
1205
|
return { ...post, isHome: true };
|
|
@@ -1211,7 +1237,11 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
|
|
|
1211
1237
|
sortedPosts.map((post) => {
|
|
1212
1238
|
return /* @__PURE__ */ React23.createElement(PostListItem2, { key: post.id, post });
|
|
1213
1239
|
}),
|
|
1214
|
-
["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React23.createElement(PostListItem2, null)
|
|
1240
|
+
["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React23.createElement(PostListItem2, null),
|
|
1241
|
+
hasNextPage && /* @__PURE__ */ React23.createElement(import_ui15.Box, { sx: {
|
|
1242
|
+
display: "flex",
|
|
1243
|
+
justifyContent: "center"
|
|
1244
|
+
} }, /* @__PURE__ */ React23.createElement(import_ui15.Button, { onClick: fetchNextPage, color: "secondary" }, isFetchingNextPage ? /* @__PURE__ */ React23.createElement(import_ui15.CircularProgress, null) : "Load More"))
|
|
1215
1245
|
)));
|
|
1216
1246
|
}
|
|
1217
1247
|
|
package/dist/index.mjs
CHANGED
|
@@ -323,10 +323,10 @@ import { __ as __15 } from "@wordpress/i18n";
|
|
|
323
323
|
// src/components/panel/posts-list/posts-collapsible-list.tsx
|
|
324
324
|
import * as React23 from "react";
|
|
325
325
|
import { PageTypeIcon as PageTypeIcon2 } from "@elementor/icons";
|
|
326
|
-
import { Skeleton, Box as Box4, List as List2 } from "@elementor/ui";
|
|
326
|
+
import { Skeleton, Box as Box4, List as List2, Button as Button4, CircularProgress as CircularProgress5 } from "@elementor/ui";
|
|
327
327
|
|
|
328
328
|
// src/hooks/use-posts.ts
|
|
329
|
-
import {
|
|
329
|
+
import { useInfiniteQuery } from "@elementor/query";
|
|
330
330
|
|
|
331
331
|
// src/api/post.ts
|
|
332
332
|
import apiFetch4 from "@wordpress/api-fetch";
|
|
@@ -340,17 +340,28 @@ var postTypesMap = {
|
|
|
340
340
|
rest_base: "pages"
|
|
341
341
|
}
|
|
342
342
|
};
|
|
343
|
-
var
|
|
343
|
+
var POST_PER_PAGE = 10;
|
|
344
|
+
var getRequest2 = async (postTypeSlug, page) => {
|
|
344
345
|
const baseUri = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
|
|
345
346
|
const keys = ["id", "type", "title", "link", "status", "user_can"];
|
|
346
347
|
const queryParams = new URLSearchParams({
|
|
347
348
|
status: "any",
|
|
348
|
-
per_page: "-1",
|
|
349
349
|
order: "asc",
|
|
350
|
+
page: page.toString(),
|
|
351
|
+
per_page: POST_PER_PAGE.toString(),
|
|
350
352
|
_fields: keys.join(",")
|
|
351
353
|
});
|
|
352
354
|
const uri = baseUri + "?" + queryParams.toString();
|
|
353
|
-
|
|
355
|
+
const result = await apiFetch4({ path: uri, parse: false });
|
|
356
|
+
const data = await result.json();
|
|
357
|
+
const totalPages = Number(result.headers.get("x-wp-totalpages"));
|
|
358
|
+
const totalPosts = Number(result.headers.get("x-wp-total"));
|
|
359
|
+
return {
|
|
360
|
+
data,
|
|
361
|
+
totalPages,
|
|
362
|
+
totalPosts,
|
|
363
|
+
currentPage: page
|
|
364
|
+
};
|
|
354
365
|
};
|
|
355
366
|
var createRequest = (postTypeSlug, newPost) => {
|
|
356
367
|
const path = `/wp/v2/${postTypesMap[postTypeSlug].rest_base}`;
|
|
@@ -390,11 +401,26 @@ var duplicateRequest = (originalPost) => {
|
|
|
390
401
|
|
|
391
402
|
// src/hooks/use-posts.ts
|
|
392
403
|
var postsQueryKey = (postTypeSlug) => ["site-navigation", "posts", postTypeSlug];
|
|
404
|
+
var flattenData = (data) => {
|
|
405
|
+
if (!data) {
|
|
406
|
+
return data;
|
|
407
|
+
}
|
|
408
|
+
const flattened = [];
|
|
409
|
+
data.pages.forEach((page) => {
|
|
410
|
+
flattened.push(...page.data);
|
|
411
|
+
});
|
|
412
|
+
return flattened;
|
|
413
|
+
};
|
|
393
414
|
function usePosts(postTypeSlug) {
|
|
394
|
-
|
|
415
|
+
const query = useInfiniteQuery({
|
|
395
416
|
queryKey: postsQueryKey(postTypeSlug),
|
|
396
|
-
queryFn: () => getRequest2(postTypeSlug)
|
|
417
|
+
queryFn: ({ pageParam = 1 }) => getRequest2(postTypeSlug, pageParam),
|
|
418
|
+
initialPageParam: 1,
|
|
419
|
+
getNextPageParam: (lastPage) => {
|
|
420
|
+
return lastPage.currentPage < lastPage.totalPages ? lastPage.currentPage + 1 : void 0;
|
|
421
|
+
}
|
|
397
422
|
});
|
|
423
|
+
return { ...query, data: { posts: flattenData(query.data), total: query.data?.pages[0]?.totalPosts ?? 0 } };
|
|
398
424
|
}
|
|
399
425
|
|
|
400
426
|
// src/contexts/post-list-context.tsx
|
|
@@ -485,7 +511,7 @@ function CollapsibleList({
|
|
|
485
511
|
unmountOnExit: true
|
|
486
512
|
},
|
|
487
513
|
/* @__PURE__ */ React7.createElement(List, { dense: true }, children)
|
|
488
|
-
), /* @__PURE__ */ React7.createElement(Divider2, { sx: {
|
|
514
|
+
), /* @__PURE__ */ React7.createElement(Divider2, { sx: { mt: 1 } }));
|
|
489
515
|
}
|
|
490
516
|
|
|
491
517
|
// src/components/panel/posts-list/post-list-item.tsx
|
|
@@ -970,10 +996,10 @@ var updateSettings = (settings) => {
|
|
|
970
996
|
};
|
|
971
997
|
|
|
972
998
|
// src/hooks/use-homepage.ts
|
|
973
|
-
import { useQuery as
|
|
999
|
+
import { useQuery as useQuery3 } from "@elementor/query";
|
|
974
1000
|
var settingsQueryKey = () => ["site-navigation", "homepage"];
|
|
975
1001
|
function useHomepage() {
|
|
976
|
-
return
|
|
1002
|
+
return useQuery3({
|
|
977
1003
|
queryKey: settingsQueryKey(),
|
|
978
1004
|
queryFn: () => getSettings()
|
|
979
1005
|
});
|
|
@@ -1171,7 +1197,7 @@ function ErrorState() {
|
|
|
1171
1197
|
// src/components/panel/posts-list/posts-collapsible-list.tsx
|
|
1172
1198
|
function PostsCollapsibleList({ isOpenByDefault = false }) {
|
|
1173
1199
|
const { type, editMode } = usePostListContext();
|
|
1174
|
-
const { data: posts, isLoading: postsLoading, isError: postsError } = usePosts(type);
|
|
1200
|
+
const { data: { posts, total }, isLoading: postsLoading, isError: postsError, fetchNextPage, hasNextPage, isFetchingNextPage } = usePosts(type);
|
|
1175
1201
|
const { data: homepageId } = useHomepage();
|
|
1176
1202
|
if (postsError) {
|
|
1177
1203
|
return /* @__PURE__ */ React23.createElement(ErrorState, null);
|
|
@@ -1187,7 +1213,7 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
|
|
|
1187
1213
|
/* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 4 }, animation: "wave", variant: "rounded", width: "110px", height: "28px" })
|
|
1188
1214
|
), /* @__PURE__ */ React23.createElement(Box4, null, /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "100%", height: "24px" }), /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" }), /* @__PURE__ */ React23.createElement(Skeleton, { sx: { my: 3 }, animation: "wave", variant: "rounded", width: "70%", height: "24px" })));
|
|
1189
1215
|
}
|
|
1190
|
-
const label = `${postTypesMap[type].labels.plural_name} (${
|
|
1216
|
+
const label = `${postTypesMap[type].labels.plural_name} (${total.toString()})`;
|
|
1191
1217
|
const mappedPosts = posts.map((post) => {
|
|
1192
1218
|
if (post.id === homepageId) {
|
|
1193
1219
|
return { ...post, isHome: true };
|
|
@@ -1225,7 +1251,11 @@ function PostsCollapsibleList({ isOpenByDefault = false }) {
|
|
|
1225
1251
|
sortedPosts.map((post) => {
|
|
1226
1252
|
return /* @__PURE__ */ React23.createElement(PostListItem2, { key: post.id, post });
|
|
1227
1253
|
}),
|
|
1228
|
-
["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React23.createElement(PostListItem2, null)
|
|
1254
|
+
["duplicate", "create"].includes(editMode.mode) && /* @__PURE__ */ React23.createElement(PostListItem2, null),
|
|
1255
|
+
hasNextPage && /* @__PURE__ */ React23.createElement(Box4, { sx: {
|
|
1256
|
+
display: "flex",
|
|
1257
|
+
justifyContent: "center"
|
|
1258
|
+
} }, /* @__PURE__ */ React23.createElement(Button4, { onClick: fetchNextPage, color: "secondary" }, isFetchingNextPage ? /* @__PURE__ */ React23.createElement(CircularProgress5, null) : "Load More"))
|
|
1229
1259
|
)));
|
|
1230
1260
|
}
|
|
1231
1261
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-site-navigation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Elementor Team",
|
|
6
6
|
"homepage": "https://elementor.com/",
|
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
"dev": "tsup --config=../../tsup.dev.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@elementor/editor-app-bar": "^0.9.
|
|
35
|
+
"@elementor/editor-app-bar": "^0.9.8",
|
|
36
36
|
"@elementor/editor-documents": "^0.10.1",
|
|
37
|
-
"@elementor/editor-panels": "^0.4.
|
|
37
|
+
"@elementor/editor-panels": "^0.4.7",
|
|
38
38
|
"@elementor/editor-v1-adapters": "^0.6.0",
|
|
39
39
|
"@elementor/env": "^0.3.2",
|
|
40
40
|
"@elementor/icons": "^0.7.2",
|
|
41
|
-
"@elementor/query": "^0.
|
|
41
|
+
"@elementor/query": "^0.2.0",
|
|
42
42
|
"@elementor/ui": "^1.4.61",
|
|
43
43
|
"@wordpress/api-fetch": "^6.42.0",
|
|
44
44
|
"@wordpress/i18n": "^4.45.0",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"elementor": {
|
|
51
51
|
"type": "extension"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "780188b3bf19f2d02edabce30374f89b1fecbc07"
|
|
54
54
|
}
|
package/src/api/post.ts
CHANGED
|
@@ -24,21 +24,49 @@ export const postTypesMap = {
|
|
|
24
24
|
},
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
export const
|
|
27
|
+
export const POST_PER_PAGE = 10;
|
|
28
|
+
|
|
29
|
+
type WpPostsResponse = {
|
|
30
|
+
json: () => Promise<Post[]>,
|
|
31
|
+
headers: {
|
|
32
|
+
get: ( key: string ) => string | null,
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PostsResponse = {
|
|
37
|
+
data: Post[],
|
|
38
|
+
totalPages: number,
|
|
39
|
+
totalPosts: number,
|
|
40
|
+
currentPage: number,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getRequest = async ( postTypeSlug: Slug, page: number ): Promise<PostsResponse> => {
|
|
28
44
|
const baseUri = `/wp/v2/${ postTypesMap[ postTypeSlug ].rest_base }`;
|
|
29
45
|
|
|
30
46
|
const keys: Array<keyof Post> = [ 'id', 'type', 'title', 'link', 'status', 'user_can' ];
|
|
31
47
|
|
|
32
48
|
const queryParams = new URLSearchParams( {
|
|
33
49
|
status: 'any',
|
|
34
|
-
per_page: '-1',
|
|
35
50
|
order: 'asc',
|
|
51
|
+
page: page.toString(),
|
|
52
|
+
per_page: POST_PER_PAGE.toString(),
|
|
36
53
|
_fields: keys.join( ',' ),
|
|
37
54
|
} );
|
|
38
55
|
|
|
39
56
|
const uri = baseUri + '?' + queryParams.toString();
|
|
40
57
|
|
|
41
|
-
|
|
58
|
+
const result = await apiFetch<WpPostsResponse>( { path: uri, parse: false } );
|
|
59
|
+
const data = await result.json();
|
|
60
|
+
|
|
61
|
+
const totalPages = Number( result.headers.get( 'x-wp-totalpages' ) );
|
|
62
|
+
const totalPosts = Number( result.headers.get( 'x-wp-total' ) );
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
data,
|
|
66
|
+
totalPages,
|
|
67
|
+
totalPosts,
|
|
68
|
+
currentPage: page,
|
|
69
|
+
};
|
|
42
70
|
};
|
|
43
71
|
|
|
44
72
|
export const createRequest = ( postTypeSlug: Slug, newPost: NewPost ) => {
|
|
@@ -4,6 +4,7 @@ import PostsCollapsibleList from '../posts-collapsible-list';
|
|
|
4
4
|
import { createMockDocument, renderWithQuery } from 'test-utils';
|
|
5
5
|
import { PostListContextProvider } from '../../../../contexts/post-list-context';
|
|
6
6
|
import { __useActiveDocument as useActiveDocument } from '@elementor/editor-documents';
|
|
7
|
+
import { usePosts } from '../../../../hooks/use-posts';
|
|
7
8
|
|
|
8
9
|
const mockMutateAsync = jest.fn();
|
|
9
10
|
jest.mock( '../../../../hooks/use-posts-actions', () => ( {
|
|
@@ -23,10 +24,13 @@ jest.mock( '../../../../hooks/use-posts', () => ( {
|
|
|
23
24
|
__esModule: true,
|
|
24
25
|
usePosts: jest.fn( () => ( {
|
|
25
26
|
isLoading: false,
|
|
26
|
-
data:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
data: {
|
|
28
|
+
posts: [
|
|
29
|
+
{ id: 1, type: 'page', title: { rendered: 'Home' }, status: 'publish', link: 'www.test.demo', user_can: { edit: true, delete: true } },
|
|
30
|
+
{ id: 2, type: 'page', title: { rendered: 'About' }, status: 'draft', link: 'www.test.demo', user_can: { edit: true, delete: true } },
|
|
31
|
+
],
|
|
32
|
+
total: 2,
|
|
33
|
+
},
|
|
30
34
|
} ) ),
|
|
31
35
|
} ) );
|
|
32
36
|
|
|
@@ -156,4 +160,42 @@ describe( '@elementor/editor-site-navigation - PostsCollapsibleList', () => {
|
|
|
156
160
|
expect( input ).toBeInTheDocument();
|
|
157
161
|
expect( input ).toHaveValue( 'Home copy' );
|
|
158
162
|
} );
|
|
163
|
+
|
|
164
|
+
it( 'Should not render load button when there is no next page', () => {
|
|
165
|
+
// Arrange.
|
|
166
|
+
jest.mocked( usePosts ).mockReturnValue( {
|
|
167
|
+
hasNextPage: false,
|
|
168
|
+
isLoading: false,
|
|
169
|
+
data: {
|
|
170
|
+
posts: [
|
|
171
|
+
{ id: 1, type: 'page', title: { rendered: 'Home' }, status: 'publish', link: 'www.test.demo', user_can: { edit: true, delete: true } },
|
|
172
|
+
{ id: 2, type: 'page', title: { rendered: 'About' }, status: 'draft', link: 'www.test.demo', user_can: { edit: true, delete: true } },
|
|
173
|
+
],
|
|
174
|
+
total: 2,
|
|
175
|
+
},
|
|
176
|
+
} as ReturnType<typeof usePosts> );
|
|
177
|
+
renderWithQuery( <PostsCollapsibleList isOpenByDefault={ true } /> );
|
|
178
|
+
|
|
179
|
+
// Assert.
|
|
180
|
+
expect( screen.queryByRole( 'button', { name: 'Load More' } ) ).not.toBeInTheDocument();
|
|
181
|
+
} );
|
|
182
|
+
|
|
183
|
+
it( 'Should render load button when there is next page', () => {
|
|
184
|
+
// Arrange.
|
|
185
|
+
jest.mocked( usePosts ).mockReturnValue( {
|
|
186
|
+
hasNextPage: true,
|
|
187
|
+
isLoading: false,
|
|
188
|
+
data: {
|
|
189
|
+
posts: [
|
|
190
|
+
{ id: 1, type: 'page', title: { rendered: 'Home' }, status: 'publish', link: 'www.test.demo', user_can: { edit: true, delete: true } },
|
|
191
|
+
{ id: 2, type: 'page', title: { rendered: 'About' }, status: 'draft', link: 'www.test.demo', user_can: { edit: true, delete: true } },
|
|
192
|
+
],
|
|
193
|
+
total: 2,
|
|
194
|
+
},
|
|
195
|
+
} as ReturnType<typeof usePosts> );
|
|
196
|
+
renderWithQuery( <PostsCollapsibleList isOpenByDefault={ true } /> );
|
|
197
|
+
|
|
198
|
+
// Assert.
|
|
199
|
+
expect( screen.getByRole( 'button', { name: 'Load More' } ) ).toBeInTheDocument();
|
|
200
|
+
} );
|
|
159
201
|
} );
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { PageTypeIcon } from '@elementor/icons';
|
|
3
|
-
import { Skeleton, Box, List } from '@elementor/ui';
|
|
3
|
+
import { Skeleton, Box, List, Button, CircularProgress } from '@elementor/ui';
|
|
4
4
|
import { usePosts } from '../../../hooks/use-posts';
|
|
5
5
|
import { usePostListContext } from '../../../contexts/post-list-context';
|
|
6
6
|
import { postTypesMap } from '../../../api/post';
|
|
@@ -16,7 +16,7 @@ type Props = {
|
|
|
16
16
|
|
|
17
17
|
export default function PostsCollapsibleList( { isOpenByDefault = false }: Props ) {
|
|
18
18
|
const { type, editMode } = usePostListContext();
|
|
19
|
-
const { data: posts, isLoading: postsLoading, isError: postsError } = usePosts( type );
|
|
19
|
+
const { data: { posts, total }, isLoading: postsLoading, isError: postsError, fetchNextPage, hasNextPage, isFetchingNextPage } = usePosts( type );
|
|
20
20
|
const { data: homepageId } = useHomepage();
|
|
21
21
|
|
|
22
22
|
if ( postsError ) {
|
|
@@ -43,7 +43,7 @@ export default function PostsCollapsibleList( { isOpenByDefault = false }: Props
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const label = `${ postTypesMap[ type ].labels.plural_name } (${
|
|
46
|
+
const label = `${ postTypesMap[ type ].labels.plural_name } (${ total.toString() })`;
|
|
47
47
|
|
|
48
48
|
const mappedPosts = posts.map( ( post ) => {
|
|
49
49
|
if ( post.id === homepageId ) {
|
|
@@ -91,6 +91,16 @@ export default function PostsCollapsibleList( { isOpenByDefault = false }: Props
|
|
|
91
91
|
[ 'duplicate', 'create' ].includes( editMode.mode ) &&
|
|
92
92
|
<PostListItem />
|
|
93
93
|
}
|
|
94
|
+
{ hasNextPage &&
|
|
95
|
+
<Box sx={ {
|
|
96
|
+
display: 'flex',
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
} }>
|
|
99
|
+
<Button onClick={ fetchNextPage } color="secondary">
|
|
100
|
+
{ isFetchingNextPage ? <CircularProgress /> : 'Load More' }
|
|
101
|
+
</Button>
|
|
102
|
+
</Box>
|
|
103
|
+
}
|
|
94
104
|
</CollapsibleList>
|
|
95
105
|
</List>
|
|
96
106
|
</>
|
|
@@ -2,42 +2,76 @@ import { waitFor } from '@testing-library/react';
|
|
|
2
2
|
import apiFetch from '@wordpress/api-fetch';
|
|
3
3
|
import { renderHookWithQuery } from 'test-utils';
|
|
4
4
|
import { usePosts } from '../use-posts';
|
|
5
|
+
import { POST_PER_PAGE } from '../../api/post';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
const MOCK_POST_PER_PAGE = 2;
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
jest.mock( '@wordpress/api-fetch', () => ( {
|
|
10
|
+
default: jest.fn( ( param ) => {
|
|
11
|
+
const pages = [
|
|
12
|
+
{ id: 1, type: 'page', title: { rendered: 'Home' }, status: 'draft', link: 'www.test.demo' },
|
|
13
|
+
{ id: 2, type: 'page', title: { rendered: 'About' }, status: 'publish', link: 'www.test.demo' },
|
|
14
|
+
{ id: 3, type: 'page', title: { rendered: 'Services' }, status: 'publish', link: 'www.test.demo', isHome: true },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const url = new URL( 'http:/example.com' + param.path );
|
|
18
|
+
|
|
19
|
+
const page = Number( url.searchParams.get( 'page' ) );
|
|
12
20
|
|
|
21
|
+
const startIndex = ( page - 1 ) * MOCK_POST_PER_PAGE;
|
|
22
|
+
const endIndex = startIndex + MOCK_POST_PER_PAGE;
|
|
23
|
+
const paginatedPages = pages.slice( startIndex, endIndex );
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
json: () => Promise.resolve( paginatedPages ),
|
|
27
|
+
headers: {
|
|
28
|
+
get: ( header: string ) => {
|
|
29
|
+
switch ( header ) {
|
|
30
|
+
case 'x-wp-totalpages':
|
|
31
|
+
return Math.ceil( pages.length / MOCK_POST_PER_PAGE );
|
|
32
|
+
case 'x-wp-total':
|
|
33
|
+
return pages.length;
|
|
34
|
+
default:
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
} ),
|
|
41
|
+
__esModule: true,
|
|
42
|
+
} ) );
|
|
43
|
+
|
|
44
|
+
describe( '@elementor/site-settings/use-posts', () => {
|
|
13
45
|
afterEach( () => {
|
|
14
46
|
jest.clearAllMocks();
|
|
15
47
|
} );
|
|
16
48
|
|
|
17
49
|
it( 'usePosts hook should return posts list by type', async () => {
|
|
18
|
-
//
|
|
50
|
+
//Arrange
|
|
19
51
|
const pages = [
|
|
20
52
|
{ id: 1, type: 'page', title: { rendered: 'Home' }, status: 'draft', link: 'www.test.demo' },
|
|
21
53
|
{ id: 2, type: 'page', title: { rendered: 'About' }, status: 'publish', link: 'www.test.demo' },
|
|
22
54
|
{ id: 3, type: 'page', title: { rendered: 'Services' }, status: 'publish', link: 'www.test.demo', isHome: true },
|
|
23
55
|
];
|
|
24
|
-
jest.mocked( apiFetch ).mockImplementation( () => Promise.resolve( pages ) );
|
|
25
56
|
|
|
26
57
|
// Act.
|
|
27
58
|
const { component } = renderHookWithQuery( () => usePosts( 'page' ) );
|
|
28
59
|
|
|
29
60
|
// Assert.
|
|
30
|
-
const expectedPath = `/wp/v2/pages?status=any&
|
|
61
|
+
const expectedPath = `/wp/v2/pages?status=any&order=asc&page=1&per_page=${ POST_PER_PAGE }&_fields=${ encodeURIComponent( 'id,type,title,link,status,user_can' ) }`;
|
|
31
62
|
|
|
32
63
|
expect( apiFetch ).toHaveBeenCalledWith( {
|
|
64
|
+
parse: false,
|
|
33
65
|
path: expectedPath,
|
|
34
66
|
} );
|
|
35
67
|
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
36
68
|
|
|
37
69
|
await waitFor( () => {
|
|
38
|
-
|
|
70
|
+
expect( component.result.current.isLoading ).toBeFalsy();
|
|
39
71
|
} );
|
|
40
72
|
|
|
41
|
-
expect( component.result.current.data ).
|
|
73
|
+
expect( component.result.current.data.posts ).toContainEqual( pages[ 0 ] );
|
|
74
|
+
expect( component.result.current.data.posts ).toContainEqual( pages[ 1 ] );
|
|
75
|
+
expect( component.result.current.data.posts ).not.toContainEqual( pages[ 2 ] );
|
|
42
76
|
} );
|
|
43
77
|
} );
|
package/src/hooks/use-posts.ts
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getRequest, Slug } from '../api/post';
|
|
1
|
+
import { useInfiniteQuery } from '@elementor/query';
|
|
2
|
+
import { getRequest, PostsResponse, Slug } from '../api/post';
|
|
3
|
+
import { Post } from '../types';
|
|
3
4
|
|
|
4
5
|
export const postsQueryKey = ( postTypeSlug: string ) => [ 'site-navigation', 'posts', postTypeSlug ];
|
|
5
6
|
|
|
7
|
+
const flattenData = ( data?: {pages: PostsResponse[]} ) => {
|
|
8
|
+
if ( ! data ) {
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const flattened: Post[] = [];
|
|
13
|
+
|
|
14
|
+
data.pages.forEach( ( page ) => {
|
|
15
|
+
flattened.push( ...page.data );
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
return flattened;
|
|
19
|
+
};
|
|
20
|
+
|
|
6
21
|
export function usePosts( postTypeSlug: Slug ) {
|
|
7
|
-
|
|
22
|
+
const query = useInfiniteQuery( {
|
|
8
23
|
queryKey: postsQueryKey( postTypeSlug ),
|
|
9
|
-
queryFn: () => getRequest( postTypeSlug ),
|
|
24
|
+
queryFn: ( { pageParam = 1 } ) => getRequest( postTypeSlug, pageParam ),
|
|
25
|
+
initialPageParam: 1,
|
|
26
|
+
getNextPageParam: ( lastPage ) => {
|
|
27
|
+
return lastPage.currentPage < lastPage.totalPages ? lastPage.currentPage + 1 : undefined;
|
|
28
|
+
},
|
|
10
29
|
} );
|
|
30
|
+
|
|
31
|
+
return { ...query, data: { posts: flattenData( query.data ), total: query.data?.pages[ 0 ]?.totalPosts ?? 0 } };
|
|
11
32
|
}
|