index.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. import { useEffect, useRef, useState } from "react";
  2. import { Tooltip } from "antd";
  3. import cls from "classnames";
  4. import styles from "./index.module.scss";
  5. import { useDeepCompareEffect, useHover } from "ahooks";
  6. import IconFont from "@/components/icon-font";
  7. import { downloadFileUseAScript } from "@/utils/download";
  8. import { MD_DRIVE_PDF } from "@/constant/event";
  9. import { useIntl } from "react-intl";
  10. import LazyUrlMarkdown from "../url-markdown";
  11. import exitFullScreenSvg from "@/assets/pdf/exitFullScreen.svg";
  12. import fullScreenSvg from "@/assets/pdf/fullScreen.svg";
  13. import { MD_PREVIEW_TYPE } from "@/types/extract-task-type";
  14. import _ from "lodash";
  15. import { TaskIdResItem } from "@/api/extract";
  16. import useMdStore from "@/store/mdStore";
  17. import CodeMirror from "@/components/code-mirror";
  18. import { useParams } from "react-router-dom";
  19. import SaveStatus, { SaveStatusRef } from "@/components/SaveStatus";
  20. interface IMdViewerProps {
  21. md?: string;
  22. className?: string;
  23. filename?: string;
  24. url?: string;
  25. taskInfo: TaskIdResItem;
  26. curPage: number;
  27. fullScreen?: boolean;
  28. setFullScreen?: (value?: boolean) => void;
  29. }
  30. const MdViewer: React.FC<IMdViewerProps> = ({
  31. fullScreen,
  32. setFullScreen,
  33. taskInfo,
  34. className = "",
  35. curPage,
  36. }) => {
  37. const mdViewerPef = useRef<HTMLDivElement>(null);
  38. const url = taskInfo?.fullMdLink || "";
  39. const containerRef = useRef<HTMLDivElement>(null);
  40. const isHovering = useHover(containerRef);
  41. const { formatMessage } = useIntl();
  42. const [displayType, setDisplayType] = useState(MD_PREVIEW_TYPE.preview);
  43. const params = useParams();
  44. const {
  45. setAllMdContentWithAnchor,
  46. allMdContentWithAnchor,
  47. setMdUrlArr,
  48. mdContents,
  49. updateMdContent,
  50. } = useMdStore();
  51. const [lineWrap, setLineWrap] = useState(false);
  52. const threshold = 562 - 427;
  53. const statusRef = useRef<SaveStatusRef>(null);
  54. const menuList = [
  55. {
  56. name: formatMessage({ id: "extractor.markdown.preview" }),
  57. code: MD_PREVIEW_TYPE.preview,
  58. },
  59. {
  60. name: formatMessage({ id: "extractor.markdown.code" }),
  61. code: MD_PREVIEW_TYPE.code,
  62. },
  63. ];
  64. const getVisibleFromType = (str: string, type: string) => {
  65. return str === type
  66. ? "relative w-full h-full"
  67. : "w-0 h-0 overflow-hidden hidden";
  68. };
  69. const pushMdViewerScroll = (scrollType?: "instant" | "smooth") => {
  70. const container = document.getElementById(`md-container`);
  71. // md渲染的时候用一个元素包括anchor
  72. const element =
  73. displayType === MD_PREVIEW_TYPE.preview
  74. ? document.getElementById(`md-anchor-${curPage - 1}`)?.parentElement
  75. : document.getElementById(`code-${curPage - 1}`);
  76. if (element && container) {
  77. container.scrollTo({
  78. top: element.offsetTop - 124,
  79. behavior: scrollType || "smooth",
  80. });
  81. }
  82. };
  83. useEffect(() => {
  84. if (isHovering) return;
  85. pushMdViewerScroll();
  86. }, [curPage, isHovering]);
  87. useEffect(() => {
  88. pushMdViewerScroll("instant");
  89. }, [displayType]);
  90. useEffect(() => {
  91. if (!isHovering) return;
  92. const handleScroll = () => {
  93. if (!containerRef.current) return;
  94. taskInfo?.markdownUrl?.forEach((page, index) => {
  95. const element =
  96. displayType === MD_PREVIEW_TYPE.preview
  97. ? document.getElementById(`md-anchor-${index}`)?.parentElement
  98. : document.getElementById(`code-${index}`);
  99. if (element) {
  100. const rect = element.getBoundingClientRect();
  101. if (rect.top <= threshold) {
  102. document.dispatchEvent(
  103. new CustomEvent(MD_DRIVE_PDF, {
  104. detail: index,
  105. })
  106. );
  107. }
  108. }
  109. });
  110. };
  111. const container = containerRef.current;
  112. if (container) {
  113. container.addEventListener("scroll", handleScroll);
  114. }
  115. return () => {
  116. if (container) {
  117. container?.removeEventListener("scroll", handleScroll);
  118. }
  119. };
  120. }, [taskInfo, isHovering, displayType]);
  121. useDeepCompareEffect(() => {
  122. if (taskInfo?.markdownUrl) {
  123. setMdUrlArr(taskInfo?.markdownUrl);
  124. }
  125. statusRef?.current?.reset();
  126. }, [taskInfo?.markdownUrl, params?.jobID]);
  127. const handleContentChange = (val: string, index: number) => {
  128. setAllMdContentWithAnchor(val);
  129. statusRef?.current?.triggerSave();
  130. if (taskInfo?.file_key) {
  131. updateMdContent(taskInfo.file_key!, index, val);
  132. }
  133. };
  134. return (
  135. <div className={cls(className)} ref={mdViewerPef}>
  136. <div
  137. className={cls(
  138. "h-[49px] px-6 border-0 border-solid border-b-[1px] border-[#EBECF0] w-full pl-[24px] flex justify-between items-center"
  139. )}
  140. >
  141. <ul className="p-1 list-none mb-0 inline-block rounded-sm mr-auto bg-[#F4F5F9] select-none">
  142. {menuList.map((item) => (
  143. <li
  144. key={item.code}
  145. className={`mx-[0.125rem] px-2 leading-[25px] inline-block rounded-sm text-[14px] cursor-pointer text-color ${
  146. displayType === item.code && "bg-white text-primary"
  147. }`}
  148. onClick={() => setDisplayType(item.code)}
  149. >
  150. {item.name}
  151. </li>
  152. ))}
  153. </ul>
  154. <SaveStatus ref={statusRef} />
  155. {displayType === "code" && (
  156. <>
  157. <Tooltip
  158. title={
  159. fullScreen
  160. ? formatMessage({ id: "extractor.button.lineWrap" })
  161. : formatMessage({
  162. id: "extractor.button.lineWrap",
  163. })
  164. }
  165. >
  166. <IconFont
  167. type="icon-line-wrap"
  168. className={cls(
  169. "text-lg text-[#464a53] leading-0 ml-[1rem] cursor-pointer hover:bg-[#F4F5F9] p-1 rounded",
  170. lineWrap && "!text-[#0D53DE]"
  171. )}
  172. onClick={() => setLineWrap?.(!lineWrap)}
  173. />
  174. </Tooltip>
  175. <span className="w-[1px] h-[0.75rem] bg-[#D7D8DD] mx-[1rem]"></span>
  176. </>
  177. )}
  178. <Tooltip
  179. title={
  180. fullScreen
  181. ? formatMessage({ id: "extractor.button.exitFullScreen" })
  182. : formatMessage({
  183. id: "extractor.button.fullScreen",
  184. })
  185. }
  186. >
  187. <span
  188. className="cursor-pointer w-[1.5rem] user-select-none flex items-center justify-center h-[1.5rem] hover:bg-[#F4F5F9] rounded "
  189. onClick={() => setFullScreen?.(!fullScreen)}
  190. >
  191. {!fullScreen ? (
  192. <img
  193. className=" w-[1.125rem] h-[1.125rem] "
  194. src={fullScreenSvg}
  195. />
  196. ) : (
  197. <img
  198. className=" w-[1.125rem] h-[1.125rem] "
  199. src={exitFullScreenSvg}
  200. />
  201. )}
  202. </span>
  203. </Tooltip>
  204. <span className="w-[1px] h-[0.75rem] bg-[#D7D8DD] ml-[1rem]"></span>
  205. <Tooltip title={formatMessage({ id: "extractor.button.download" })}>
  206. <IconFont
  207. type="icon-xiazai"
  208. className="text-lg text-[#464a53] leading-0 ml-[1rem] cursor-pointer hover:bg-[#F4F5F9] p-1 rounded"
  209. onClick={() =>
  210. downloadFileUseAScript(
  211. url,
  212. `${_(taskInfo?.fileName).split(".").slice(0, -1).join(".")}.md`
  213. )
  214. }
  215. />
  216. </Tooltip>
  217. </div>
  218. <div
  219. className={cls(
  220. "bg-white !h-[calc(100%-60px)] px-6 py-8 overflow-auto w-full max-w-[100%]",
  221. styles.scrollBar
  222. )}
  223. id="md-container"
  224. ref={containerRef}
  225. >
  226. <div
  227. className={cls(
  228. getVisibleFromType(displayType, MD_PREVIEW_TYPE.preview)
  229. )}
  230. >
  231. <LazyUrlMarkdown
  232. markdownClass={"relative"}
  233. content={allMdContentWithAnchor}
  234. />
  235. </div>
  236. <div
  237. className={cls(getVisibleFromType(displayType, MD_PREVIEW_TYPE.code))}
  238. >
  239. {taskInfo?.markdownUrl?.map((url: string, index: number) => {
  240. const md = mdContents[url]?.content || "";
  241. if (!md) return null;
  242. return (
  243. <div key={url} id={`code-${index}`} className="opacity-1 z-[-1]">
  244. <CodeMirror
  245. value={md}
  246. lineWrapping={lineWrap}
  247. onChange={(val) => handleContentChange(val, index)}
  248. editable
  249. className="w-full h-full"
  250. />
  251. </div>
  252. );
  253. })}
  254. </div>
  255. </div>
  256. </div>
  257. );
  258. };
  259. export default MdViewer;