usePageNav.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. // ─────────────────────────────────────────────
  2. // usePageNav.ts
  3. // ─────────────────────────────────────────────
  4. /**
  5. * usePageNav
  6. *
  7. * 按「页码引用」解析并获取段落区间数据。
  8. *
  9. * pageId 形如 `M-dīghanikāya-2-10`(页码类型_书名_卷号_页码)。
  10. * 内部调用 fetchPageNav 请求 /api/v2/nav-page,并把返回的
  11. * curr.paragraph → next.paragraph 区间转换为 TypePali 需要的参数。
  12. *
  13. * @returns
  14. * - nav 页码导航数据(curr/prev/next),失败时为 undefined
  15. * - paramPali 可直接传给 TypePali 的段落参数,成功前为 undefined
  16. * - loading 请求进行中
  17. * - errorCode HTTP 错误码,无错误时为 null
  18. * - errorMessage 后端/网络错误信息
  19. */
  20. // ─────────────────────────────────────────────
  21. import { useState, useEffect } from "react";
  22. import { fetchPageNav } from "../../../api/article";
  23. import type { ArticleMode, IPageNavData } from "../../../api/article";
  24. import { HttpError } from "../../../request";
  25. export interface IPagePaliParam {
  26. id: string;
  27. book: string;
  28. para: string;
  29. mode: ArticleMode;
  30. channelId?: string;
  31. }
  32. interface IUsePageNavResult {
  33. nav?: IPageNavData;
  34. paramPali?: IPagePaliParam;
  35. loading: boolean;
  36. errorCode: number | null;
  37. errorMessage: string | null;
  38. }
  39. export const usePageNav = (
  40. pageId?: string,
  41. channelId?: string,
  42. mode: ArticleMode = "read"
  43. ): IUsePageNavResult => {
  44. const [nav, setNav] = useState<IPageNavData>();
  45. const [paramPali, setParamPali] = useState<IPagePaliParam>();
  46. const [loading, setLoading] = useState(false);
  47. const [errorCode, setErrorCode] = useState<number | null>(null);
  48. const [errorMessage, setErrorMessage] = useState<string | null>(null);
  49. useEffect(() => {
  50. if (!pageId) return;
  51. const pageParam = pageId.split("_");
  52. if (pageParam.length < 4) return;
  53. let active = true;
  54. const fetchData = async () => {
  55. setLoading(true);
  56. setErrorCode(null);
  57. setErrorMessage(null);
  58. try {
  59. const res = await fetchPageNav(pageId);
  60. if (!active) return;
  61. if (!res.ok) {
  62. setErrorCode(400);
  63. setErrorMessage(res.message);
  64. return;
  65. }
  66. const data = res.data;
  67. setNav(data);
  68. const begin = data.curr.paragraph;
  69. const end = data.next.paragraph;
  70. const para: number[] = [];
  71. for (let index = begin; index <= end; index++) {
  72. para.push(index);
  73. }
  74. setParamPali({
  75. id: `${data.curr.book}-${data.curr.paragraph}`,
  76. book: data.curr.book.toString(),
  77. para: para.join(),
  78. mode: mode,
  79. channelId: channelId,
  80. });
  81. } catch (e) {
  82. console.error("page nav fetch", e);
  83. if (!active) return;
  84. if (e instanceof HttpError) {
  85. setErrorCode(e.status);
  86. setErrorMessage(e.message);
  87. } else {
  88. setErrorCode(0);
  89. setErrorMessage("Network error");
  90. }
  91. } finally {
  92. if (active) setLoading(false);
  93. }
  94. };
  95. fetchData();
  96. return () => {
  97. active = false;
  98. };
  99. }, [pageId, channelId, mode]);
  100. return { nav, paramPali, loading, errorCode, errorMessage };
  101. };