TocPath.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { useNavigate, useSearchParams } from "react-router";
  2. import { Breadcrumb, type MenuProps, Popover, Tag, Typography } from "antd";
  3. import PaliText from "../template/Wbw/PaliText";
  4. import React, { type JSX } from "react";
  5. import { fullUrl } from "../../utils";
  6. export declare type ELinkType = "none" | "blank" | "self";
  7. interface IWidgetTocPath {
  8. data?: ITocPathNode[];
  9. trigger?: React.ReactNode;
  10. link?: ELinkType;
  11. channels?: string[];
  12. style?: React.CSSProperties;
  13. onChange?: (
  14. item: ITocPathNode,
  15. e: React.MouseEvent<HTMLSpanElement | HTMLAnchorElement, MouseEvent>
  16. ) => void;
  17. onMenuClick?: (key: string) => void;
  18. }
  19. const TocPathWidget = ({
  20. data = [],
  21. trigger,
  22. channels,
  23. style,
  24. onChange,
  25. onMenuClick,
  26. }: IWidgetTocPath): JSX.Element => {
  27. const navigate = useNavigate();
  28. const [searchParams] = useSearchParams();
  29. console.debug("TocPathWidget render");
  30. const breadcrumbItems = data.map((item, id) => {
  31. const handleClick = (
  32. e: React.MouseEvent<HTMLSpanElement | HTMLAnchorElement, MouseEvent>
  33. ) => {
  34. if (typeof onChange !== "undefined") {
  35. onChange(item, e);
  36. } else {
  37. if (item.book && item.paragraph) {
  38. const type = item.level < 8 ? "chapter" : "para";
  39. const param =
  40. type === "para" ? `&book=${item.book}&par=${item.paragraph}` : "";
  41. const channel = channels
  42. ? channels.join("_")
  43. : searchParams.get("channel");
  44. const mode = searchParams.get("mode");
  45. const urlMode = mode ? mode : "read";
  46. let url = `/article/${type}/${item.book}-${item.paragraph}?mode=${urlMode}${param}`;
  47. url += channel ? `&channel=${channel}` : "";
  48. if (e.ctrlKey || e.metaKey) {
  49. window.open(fullUrl(url), "_blank");
  50. } else {
  51. navigate(url);
  52. }
  53. }
  54. }
  55. };
  56. const title = (
  57. <Typography.Text
  58. style={{ cursor: id < data.length - 1 ? "pointer" : "unset" }}
  59. onClick={handleClick}
  60. >
  61. {item.level < 99 ? (
  62. <span
  63. style={
  64. item.level === 0
  65. ? {
  66. padding: "0 4px",
  67. backgroundColor: "rgba(128, 128, 128, 0.2)",
  68. borderRadius: 4,
  69. }
  70. : undefined
  71. }
  72. >
  73. <PaliText
  74. text={item.title}
  75. style={{ opacity: id < data.length - 1 ? 0.5 : 1 }}
  76. />
  77. </span>
  78. ) : (
  79. <Tag>{item.title}</Tag>
  80. )}
  81. </Typography.Text>
  82. );
  83. return {
  84. key: String(id),
  85. title,
  86. menu: item.menu
  87. ? {
  88. items: item.menu.filter(
  89. (i): i is NonNullable<typeof i> => i !== null
  90. ),
  91. onClick: (e: { key: string }) => {
  92. if (typeof onMenuClick !== "undefined") {
  93. onMenuClick(e.key);
  94. }
  95. },
  96. }
  97. : undefined,
  98. };
  99. });
  100. const fullPath = (
  101. <Breadcrumb
  102. items={breadcrumbItems}
  103. style={{ whiteSpace: "nowrap", width: "100%", fontSize: style?.fontSize }}
  104. />
  105. );
  106. if (typeof trigger === "undefined") {
  107. return fullPath;
  108. } else {
  109. return (
  110. <Popover placement="bottomRight" title={fullPath}>
  111. {trigger}
  112. </Popover>
  113. );
  114. }
  115. };
  116. export default TocPathWidget;