@youversion/platform-react-hooks 1.16.0 → 1.17.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +13 -0
- package/dist/useChapterNavigation.d.ts +2 -10
- package/dist/useChapterNavigation.d.ts.map +1 -1
- package/dist/useChapterNavigation.js +39 -37
- package/dist/useChapterNavigation.js.map +1 -1
- package/package.json +4 -4
- package/src/useChapterNavigation.test.tsx +160 -0
- package/src/useChapterNavigation.ts +42 -45
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
> @youversion/platform-react-hooks@1.
|
|
2
|
+
> @youversion/platform-react-hooks@1.17.0 build /home/runner/work/platform-sdk-react/platform-sdk-react/packages/hooks
|
|
3
3
|
> tsc -p tsconfig.build.json
|
|
4
4
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @youversion/platform-react-hooks
|
|
2
2
|
|
|
3
|
+
## 1.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c3d673e: added error ui for faild verses
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- a5f91bf: Add cross-book chapter navigation to Bible Reader toolbar with prev/next buttons, intro chapter support, and accessible aria-labels
|
|
12
|
+
- Updated dependencies [a5f91bf]
|
|
13
|
+
- Updated dependencies [c3d673e]
|
|
14
|
+
- @youversion/platform-core@1.17.0
|
|
15
|
+
|
|
3
16
|
## 1.16.0
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
|
@@ -7,16 +7,8 @@ interface UseChapterNavigationResult {
|
|
|
7
7
|
isLoading: boolean;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Provides navigation functionality for chapters
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @return {UseChapterNavigationResult} An object containing properties and methods for chapter navigation:
|
|
14
|
-
* - `canNavigatePrevious` (boolean): Indicates whether navigating to the previous chapter is possible.
|
|
15
|
-
* - `canNavigateNext` (boolean): Indicates whether navigating to the next chapter is possible.
|
|
16
|
-
* - `navigateToPrevious` (function): Moves to the previous chapter if possible.
|
|
17
|
-
* - `navigateToNext` (function): Moves to the next chapter if possible.
|
|
18
|
-
* - `currentChapterIndex` (number): The index of the current chapter within the list of chapters.
|
|
19
|
-
* - `isLoading` (boolean): Whether the chapter data is still loading.
|
|
10
|
+
* Provides navigation functionality for chapters across book boundaries,
|
|
11
|
+
* including intro chapter support.
|
|
20
12
|
*/
|
|
21
13
|
export declare function useChapterNavigation(): UseChapterNavigationResult;
|
|
22
14
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChapterNavigation.d.ts","sourceRoot":"","sources":["../src/useChapterNavigation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useChapterNavigation.d.ts","sourceRoot":"","sources":["../src/useChapterNavigation.ts"],"names":[],"mappings":"AAMA,UAAU,0BAA0B;IAClC,mBAAmB,EAAE,OAAO,CAAC;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,0BAA0B,CAsDjE"}
|
|
@@ -1,52 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { getAdjacentChapter } from '@youversion/platform-core';
|
|
1
3
|
import { useReaderContext } from './context/ReaderContext';
|
|
2
|
-
import {
|
|
4
|
+
import { useBooks } from './useBooks';
|
|
3
5
|
/**
|
|
4
|
-
* Provides navigation functionality for chapters
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @return {UseChapterNavigationResult} An object containing properties and methods for chapter navigation:
|
|
8
|
-
* - `canNavigatePrevious` (boolean): Indicates whether navigating to the previous chapter is possible.
|
|
9
|
-
* - `canNavigateNext` (boolean): Indicates whether navigating to the next chapter is possible.
|
|
10
|
-
* - `navigateToPrevious` (function): Moves to the previous chapter if possible.
|
|
11
|
-
* - `navigateToNext` (function): Moves to the next chapter if possible.
|
|
12
|
-
* - `currentChapterIndex` (number): The index of the current chapter within the list of chapters.
|
|
13
|
-
* - `isLoading` (boolean): Whether the chapter data is still loading.
|
|
6
|
+
* Provides navigation functionality for chapters across book boundaries,
|
|
7
|
+
* including intro chapter support.
|
|
14
8
|
*/
|
|
15
9
|
export function useChapterNavigation() {
|
|
16
|
-
const { currentChapter, currentVersion, currentBook, setChapter } = useReaderContext();
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
10
|
+
const { currentChapter, currentVersion, currentBook, setChapter, setBook } = useReaderContext();
|
|
11
|
+
const { books, loading: booksLoading } = useBooks(currentVersion?.id ?? 0, {
|
|
12
|
+
enabled: Boolean(currentVersion?.id),
|
|
13
|
+
});
|
|
14
|
+
const booksData = books?.data ?? [];
|
|
15
|
+
const bookId = currentBook?.id ?? '';
|
|
16
|
+
const chapterId = currentChapter?.id ?? '';
|
|
17
|
+
const nextResult = getAdjacentChapter(booksData, bookId, chapterId, 'next');
|
|
18
|
+
const prevResult = getAdjacentChapter(booksData, bookId, chapterId, 'previous');
|
|
19
|
+
const canNavigateNext = !booksLoading && nextResult !== null;
|
|
20
|
+
const canNavigatePrevious = !booksLoading && prevResult !== null;
|
|
21
|
+
const currentChapterIndex = currentBook?.chapters?.findIndex((c) => c.id === currentChapter?.id) ?? -1;
|
|
22
|
+
const navigate = (result) => {
|
|
23
|
+
if (!result || !booksData.length)
|
|
24
|
+
return;
|
|
25
|
+
const targetBook = booksData.find((b) => b.id === result.bookId);
|
|
26
|
+
if (!targetBook)
|
|
27
|
+
return;
|
|
28
|
+
const targetChapter = targetBook.chapters?.find((c) => c.id === result.chapterId) ??
|
|
29
|
+
(targetBook.intro?.id === result.chapterId
|
|
30
|
+
? {
|
|
31
|
+
id: targetBook.intro.id,
|
|
32
|
+
passage_id: targetBook.intro.passage_id,
|
|
33
|
+
title: targetBook.intro.title,
|
|
34
|
+
}
|
|
35
|
+
: undefined);
|
|
36
|
+
if (!targetChapter)
|
|
37
|
+
return;
|
|
38
|
+
if (targetBook.id !== currentBook?.id) {
|
|
39
|
+
setBook(targetBook);
|
|
41
40
|
}
|
|
41
|
+
setChapter(targetChapter);
|
|
42
42
|
};
|
|
43
|
+
const navigateToNext = () => navigate(nextResult);
|
|
44
|
+
const navigateToPrevious = () => navigate(prevResult);
|
|
43
45
|
return {
|
|
44
46
|
canNavigatePrevious,
|
|
45
47
|
canNavigateNext,
|
|
46
48
|
navigateToPrevious,
|
|
47
49
|
navigateToNext,
|
|
48
50
|
currentChapterIndex,
|
|
49
|
-
isLoading:
|
|
51
|
+
isLoading: booksLoading,
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
//# sourceMappingURL=useChapterNavigation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChapterNavigation.js","sourceRoot":"","sources":["../src/useChapterNavigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"useChapterNavigation.js","sourceRoot":"","sources":["../src/useChapterNavigation.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAWtC;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,gBAAgB,EAAE,CAAC;IAEhG,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,QAAQ,CAAC,cAAc,EAAE,EAAE,IAAI,CAAC,EAAE;QACzE,OAAO,EAAE,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;KACrC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC;IAE3C,MAAM,UAAU,GAAG,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IAEhF,MAAM,eAAe,GAAG,CAAC,YAAY,IAAI,UAAU,KAAK,IAAI,CAAC;IAC7D,MAAM,mBAAmB,GAAG,CAAC,YAAY,IAAI,UAAU,KAAK,IAAI,CAAC;IAEjE,MAAM,mBAAmB,GACvB,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7E,MAAM,QAAQ,GAAG,CAAC,MAAoD,EAAE,EAAE;QACxE,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM;YAAE,OAAO;QAEzC,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC;QACjE,IAAI,CAAC,UAAU;YAAE,OAAO;QAExB,MAAM,aAAa,GACjB,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,SAAS,CAAC;YAC3D,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,KAAK,MAAM,CAAC,SAAS;gBACxC,CAAC,CAAC;oBACE,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE;oBACvB,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,UAAU;oBACvC,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK;iBAC9B;gBACH,CAAC,CAAC,SAAS,CAAC,CAAC;QACjB,IAAI,CAAC,aAAa;YAAE,OAAO;QAE3B,IAAI,UAAU,CAAC,EAAE,KAAK,WAAW,EAAE,EAAE,EAAE,CAAC;YACtC,OAAO,CAAC,UAAU,CAAC,CAAC;QACtB,CAAC;QACD,UAAU,CAAC,aAAa,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,kBAAkB,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAEtD,OAAO;QACL,mBAAmB;QACnB,eAAe;QACf,kBAAkB;QAClB,cAAc;QACd,mBAAmB;QACnB,SAAS,EAAE,YAAY;KACxB,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youversion/platform-react-hooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@youversion/platform-core": "1.
|
|
25
|
+
"@youversion/platform-core": "1.17.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"react": ">=19.1.0 <20.0.0"
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"jsdom": "27.0.1",
|
|
38
38
|
"typescript": "5.9.3",
|
|
39
39
|
"vitest": "4.0.4",
|
|
40
|
-
"@internal/
|
|
41
|
-
"@internal/
|
|
40
|
+
"@internal/tsconfig": "0.0.0",
|
|
41
|
+
"@internal/eslint-config": "0.0.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"dev": "tsc --watch",
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { renderHook, act } from '@testing-library/react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useChapterNavigation } from './useChapterNavigation';
|
|
5
|
+
import { useReaderContext } from './context/ReaderContext';
|
|
6
|
+
import { ReaderProvider } from './context/ReaderProvider';
|
|
7
|
+
import { createMockBook, createMockVersion } from './__tests__/mocks/bibles';
|
|
8
|
+
import type { BibleBook, BibleChapter } from '@youversion/platform-core';
|
|
9
|
+
|
|
10
|
+
function makeChapters(bookId: string, count: number): BibleChapter[] {
|
|
11
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
12
|
+
id: (i + 1).toString(),
|
|
13
|
+
passage_id: `${bookId}.${i + 1}`,
|
|
14
|
+
title: (i + 1).toString(),
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const genChapters = makeChapters('GEN', 50);
|
|
19
|
+
const exoChapters = makeChapters('EXO', 40);
|
|
20
|
+
const revChapters = makeChapters('REV', 22);
|
|
21
|
+
|
|
22
|
+
const mockBooks: BibleBook[] = [
|
|
23
|
+
createMockBook({
|
|
24
|
+
id: 'GEN',
|
|
25
|
+
title: 'Genesis',
|
|
26
|
+
chapters: genChapters,
|
|
27
|
+
intro: { id: 'INTRO', passage_id: 'GEN.INTRO', title: 'Intro' },
|
|
28
|
+
}),
|
|
29
|
+
createMockBook({
|
|
30
|
+
id: 'EXO',
|
|
31
|
+
title: 'Exodus',
|
|
32
|
+
canon: 'old_testament',
|
|
33
|
+
chapters: exoChapters,
|
|
34
|
+
}),
|
|
35
|
+
createMockBook({
|
|
36
|
+
id: 'REV',
|
|
37
|
+
title: 'Revelation',
|
|
38
|
+
canon: 'new_testament',
|
|
39
|
+
chapters: revChapters,
|
|
40
|
+
}),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const mockUseBooks = vi.fn();
|
|
44
|
+
vi.mock('./useBooks', () => ({
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
46
|
+
useBooks: (...args: unknown[]) => mockUseBooks(...args),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
function useNavWithContext() {
|
|
50
|
+
const nav = useChapterNavigation();
|
|
51
|
+
const ctx = useReaderContext();
|
|
52
|
+
return { nav, ctx };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function wrapper(book: BibleBook, chapter: BibleChapter) {
|
|
56
|
+
return ({ children }: { children: React.ReactNode }) => (
|
|
57
|
+
<ReaderProvider
|
|
58
|
+
currentVersion={createMockVersion()}
|
|
59
|
+
currentBook={book}
|
|
60
|
+
currentChapter={chapter}
|
|
61
|
+
currentVerse={null}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</ReaderProvider>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const genBook = mockBooks[0]!;
|
|
69
|
+
const exoBook = mockBooks[1]!;
|
|
70
|
+
const revBook = mockBooks[2]!;
|
|
71
|
+
|
|
72
|
+
describe('useChapterNavigation', () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
mockUseBooks.mockReturnValue({
|
|
75
|
+
books: { data: mockBooks },
|
|
76
|
+
loading: false,
|
|
77
|
+
error: null,
|
|
78
|
+
refetch: vi.fn(),
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('navigateToNext within same book updates chapter, keeps book', () => {
|
|
83
|
+
const { result } = renderHook(useNavWithContext, {
|
|
84
|
+
wrapper: wrapper(genBook, genChapters[0]!),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
act(() => result.current.nav.navigateToNext());
|
|
88
|
+
|
|
89
|
+
expect(result.current.ctx.currentChapter.id).toBe('2');
|
|
90
|
+
expect(result.current.ctx.currentBook.id).toBe('GEN');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('navigateToNext cross-book updates both book and chapter', () => {
|
|
94
|
+
const { result } = renderHook(useNavWithContext, {
|
|
95
|
+
wrapper: wrapper(genBook, genChapters.at(-1)!),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
act(() => result.current.nav.navigateToNext());
|
|
99
|
+
|
|
100
|
+
expect(result.current.ctx.currentBook.id).toBe('EXO');
|
|
101
|
+
expect(result.current.ctx.currentChapter.id).toBe('1');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('navigateToPrevious to intro sets chapter to INTRO, keeps book', () => {
|
|
105
|
+
const { result } = renderHook(useNavWithContext, {
|
|
106
|
+
wrapper: wrapper(genBook, genChapters[0]!),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
act(() => result.current.nav.navigateToPrevious());
|
|
110
|
+
|
|
111
|
+
expect(result.current.ctx.currentChapter.id).toBe('INTRO');
|
|
112
|
+
expect(result.current.ctx.currentBook.id).toBe('GEN');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('navigateToPrevious cross-book updates both book and chapter', () => {
|
|
116
|
+
const { result } = renderHook(useNavWithContext, {
|
|
117
|
+
wrapper: wrapper(exoBook, exoChapters[0]!),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
act(() => result.current.nav.navigateToPrevious());
|
|
121
|
+
|
|
122
|
+
expect(result.current.ctx.currentBook.id).toBe('GEN');
|
|
123
|
+
expect(result.current.ctx.currentChapter.id).toBe('50');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('canNavigatePrevious is false at Bible start', () => {
|
|
127
|
+
const introChapter: BibleChapter = { id: 'INTRO', passage_id: 'GEN.INTRO', title: 'Intro' };
|
|
128
|
+
|
|
129
|
+
const { result } = renderHook(useNavWithContext, {
|
|
130
|
+
wrapper: wrapper(genBook, introChapter),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(result.current.nav.canNavigatePrevious).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('canNavigateNext is false at Bible end', () => {
|
|
137
|
+
const { result } = renderHook(useNavWithContext, {
|
|
138
|
+
wrapper: wrapper(revBook, revChapters[21]!),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(result.current.nav.canNavigateNext).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('both disabled while loading', () => {
|
|
145
|
+
mockUseBooks.mockReturnValue({
|
|
146
|
+
books: null,
|
|
147
|
+
loading: true,
|
|
148
|
+
error: null,
|
|
149
|
+
refetch: vi.fn(),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const { result } = renderHook(useNavWithContext, {
|
|
153
|
+
wrapper: wrapper(genBook, genChapters[0]!),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(result.current.nav.canNavigateNext).toBe(false);
|
|
157
|
+
expect(result.current.nav.canNavigatePrevious).toBe(false);
|
|
158
|
+
expect(result.current.nav.isLoading).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { getAdjacentChapter } from '@youversion/platform-core';
|
|
1
4
|
import { useReaderContext } from './context/ReaderContext';
|
|
2
|
-
import {
|
|
5
|
+
import { useBooks } from './useBooks';
|
|
3
6
|
|
|
4
7
|
interface UseChapterNavigationResult {
|
|
5
8
|
canNavigatePrevious: boolean;
|
|
@@ -11,67 +14,61 @@ interface UseChapterNavigationResult {
|
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
/**
|
|
14
|
-
* Provides navigation functionality for chapters
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @return {UseChapterNavigationResult} An object containing properties and methods for chapter navigation:
|
|
18
|
-
* - `canNavigatePrevious` (boolean): Indicates whether navigating to the previous chapter is possible.
|
|
19
|
-
* - `canNavigateNext` (boolean): Indicates whether navigating to the next chapter is possible.
|
|
20
|
-
* - `navigateToPrevious` (function): Moves to the previous chapter if possible.
|
|
21
|
-
* - `navigateToNext` (function): Moves to the next chapter if possible.
|
|
22
|
-
* - `currentChapterIndex` (number): The index of the current chapter within the list of chapters.
|
|
23
|
-
* - `isLoading` (boolean): Whether the chapter data is still loading.
|
|
17
|
+
* Provides navigation functionality for chapters across book boundaries,
|
|
18
|
+
* including intro chapter support.
|
|
24
19
|
*/
|
|
25
20
|
export function useChapterNavigation(): UseChapterNavigationResult {
|
|
26
|
-
const { currentChapter, currentVersion, currentBook, setChapter } = useReaderContext();
|
|
21
|
+
const { currentChapter, currentVersion, currentBook, setChapter, setBook } = useReaderContext();
|
|
27
22
|
|
|
28
|
-
const
|
|
23
|
+
const { books, loading: booksLoading } = useBooks(currentVersion?.id ?? 0, {
|
|
24
|
+
enabled: Boolean(currentVersion?.id),
|
|
25
|
+
});
|
|
29
26
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{ enabled: Boolean(bookIdentifier && currentVersion?.id) },
|
|
34
|
-
);
|
|
27
|
+
const booksData = books?.data ?? [];
|
|
28
|
+
const bookId = currentBook?.id ?? '';
|
|
29
|
+
const chapterId = currentChapter?.id ?? '';
|
|
35
30
|
|
|
36
|
-
const
|
|
31
|
+
const nextResult = getAdjacentChapter(booksData, bookId, chapterId, 'next');
|
|
32
|
+
const prevResult = getAdjacentChapter(booksData, bookId, chapterId, 'previous');
|
|
37
33
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
);
|
|
34
|
+
const canNavigateNext = !booksLoading && nextResult !== null;
|
|
35
|
+
const canNavigatePrevious = !booksLoading && prevResult !== null;
|
|
41
36
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
chapters?.data &&
|
|
45
|
-
currentChapterIndex !== -1 &&
|
|
46
|
-
currentChapterIndex < chapters.data.length - 1,
|
|
47
|
-
);
|
|
37
|
+
const currentChapterIndex =
|
|
38
|
+
currentBook?.chapters?.findIndex((c) => c.id === currentChapter?.id) ?? -1;
|
|
48
39
|
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
const previousChapter = chapters.data[currentChapterIndex - 1];
|
|
52
|
-
if (previousChapter) {
|
|
53
|
-
// Use the chapter as-is since it already has all required fields
|
|
54
|
-
setChapter(previousChapter);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
40
|
+
const navigate = (result: { bookId: string; chapterId: string } | null) => {
|
|
41
|
+
if (!result || !booksData.length) return;
|
|
58
42
|
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
const targetBook = booksData.find((b) => b.id === result.bookId);
|
|
44
|
+
if (!targetBook) return;
|
|
45
|
+
|
|
46
|
+
const targetChapter =
|
|
47
|
+
targetBook.chapters?.find((c) => c.id === result.chapterId) ??
|
|
48
|
+
(targetBook.intro?.id === result.chapterId
|
|
49
|
+
? {
|
|
50
|
+
id: targetBook.intro.id,
|
|
51
|
+
passage_id: targetBook.intro.passage_id,
|
|
52
|
+
title: targetBook.intro.title,
|
|
53
|
+
}
|
|
54
|
+
: undefined);
|
|
55
|
+
if (!targetChapter) return;
|
|
56
|
+
|
|
57
|
+
if (targetBook.id !== currentBook?.id) {
|
|
58
|
+
setBook(targetBook);
|
|
66
59
|
}
|
|
60
|
+
setChapter(targetChapter);
|
|
67
61
|
};
|
|
68
62
|
|
|
63
|
+
const navigateToNext = () => navigate(nextResult);
|
|
64
|
+
const navigateToPrevious = () => navigate(prevResult);
|
|
65
|
+
|
|
69
66
|
return {
|
|
70
67
|
canNavigatePrevious,
|
|
71
68
|
canNavigateNext,
|
|
72
69
|
navigateToPrevious,
|
|
73
70
|
navigateToNext,
|
|
74
71
|
currentChapterIndex,
|
|
75
|
-
isLoading:
|
|
72
|
+
isLoading: booksLoading,
|
|
76
73
|
};
|
|
77
74
|
}
|