@zjlab-fe/data-hub-ui 0.24.0 → 0.25.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.
@@ -1,6 +1,10 @@
1
1
  export interface IFilePreviewProps {
2
+ /** 文件扩展名,如果传入了该值,则以该扩展名的格式展示,否则按照filePath的扩展名展示 */
3
+ ext?: string;
2
4
  /** 文件url */
3
5
  filePath?: string;
6
+ /** 自定义HTTP请求头,比如:认证信息 */
7
+ httpHeaders?: Record<string, string>;
4
8
  /**
5
9
  * 已有数据,展示成Table
6
10
  */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 从 esm.sh CDN 动态加载 ESM 模块,带缓存
3
+ * @param name - npm 包名
4
+ * @param version - 版本号(如 '7' 表示 7.x latest)
5
+ */
6
+ export declare function loadPlugin<T = any>(name: string, version: string): Promise<T>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 从 CDN 加载全局 MathJax 脚本
3
+ * 配置支持 $...$ 和 $$...$$ 行内/块级公式
4
+ */
5
+ export declare function loadMathjax(): Promise<void>;
6
+ /**
7
+ * 在指定容器内对 MathJax 公式重新排版
8
+ * 通常在 DOM 更新后调用
9
+ */
10
+ export declare function typesetMathjax(container: HTMLElement): Promise<void>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * remark 插件:将 remark-math 解析的 math/inlineMath mdast 节点转回
3
+ * 带 $ 定界符的 text 节点,供全局 MathJax 后处理渲染
4
+ *
5
+ * 必须在 remark-rehype 转换之前执行,因为 mdast→hAST 阶段
6
+ * 无法识别 math 节点类型,会静默丢弃
7
+ */
8
+ export declare function remarkMathToText(): (tree: any) => void;
@@ -0,0 +1,14 @@
1
+ export interface MarkdownPlugins {
2
+ rehypeRaw: any;
3
+ remarkGfm: any;
4
+ remarkMath: any;
5
+ rehypeKatex: any;
6
+ }
7
+ interface UsePluginsResult {
8
+ plugins: MarkdownPlugins | null;
9
+ loading: boolean;
10
+ error: Error | null;
11
+ }
12
+ /** 从 CDN 异步加载 markdown 渲染所需的 unified 插件 */
13
+ export declare function useMarkdownPlugins(): UsePluginsResult;
14
+ export {};
@@ -5,4 +5,4 @@ export declare function appendAsset(assetUrlArr: string[]): Promise<unknown>;
5
5
  * @param {string} fileUrl - 文件地址
6
6
  * @returns {Promise<string>} 解码后的正常字符串
7
7
  */
8
- export declare function fetchFileWithAutoCode(fileUrl: string): Promise<string>;
8
+ export declare function fetchFileWithAutoCode(fileUrl: string, httpHeaders?: Record<string, string>): Promise<string>;
@@ -34,7 +34,7 @@ function getEncoding(blob) {
34
34
  function ExcelPreview(props) {
35
35
  var _a;
36
36
  const ref = useRef(null);
37
- const ext = getFileExt(props.filePath);
37
+ const ext = props.ext || getFileExt(props.filePath);
38
38
  const [csvData, setCsvData] = useState();
39
39
  const [csvContainerHeight, setCsvContainerHeight] = useState();
40
40
  const [csvContainerWidth, setCsvContainerWidth] = useState();
@@ -45,7 +45,11 @@ function ExcelPreview(props) {
45
45
  'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/npm/xlsx/0.18.5/dist/xlsx.full.min.js',
46
46
  'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/npm/papaparse/5.5.2/papaparse.min.js',
47
47
  ]).then(() => __awaiter(this, void 0, void 0, function* () {
48
- fetch(props.filePath)
48
+ const fetchOptions = {};
49
+ if (props.httpHeaders) {
50
+ fetchOptions.headers = props.httpHeaders;
51
+ }
52
+ fetch(props.filePath, fetchOptions)
49
53
  .then((response) => __awaiter(this, void 0, void 0, function* () {
50
54
  if (ext === 'csv') {
51
55
  const blob = yield response.blob();
@@ -1,7 +1,48 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { wrap, img } from './index.module.scss.js';
3
+ import { useState, useRef, useEffect } from 'react';
3
4
 
4
5
  function ImgPreview(props) {
6
+ const { filePath, httpHeaders } = props;
7
+ const [imgSrc, setImgSrc] = useState('');
8
+ const objectUrlRef = useRef('');
9
+ useEffect(() => {
10
+ if (!filePath) {
11
+ return;
12
+ }
13
+ if (!httpHeaders || Object.keys(httpHeaders).length === 0) {
14
+ setImgSrc(filePath);
15
+ return;
16
+ }
17
+ const controller = new AbortController();
18
+ fetch(filePath, {
19
+ headers: httpHeaders,
20
+ signal: controller.signal,
21
+ })
22
+ .then((res) => {
23
+ if (!res.ok) {
24
+ throw new Error(`HTTP ${res.status}`);
25
+ }
26
+ return res.blob();
27
+ })
28
+ .then((blob) => {
29
+ const url = URL.createObjectURL(blob);
30
+ objectUrlRef.current = url;
31
+ setImgSrc(url);
32
+ })
33
+ .catch((e) => {
34
+ var _a;
35
+ if (e.name !== 'AbortError') {
36
+ (_a = props === null || props === void 0 ? void 0 : props.onError) === null || _a === void 0 ? void 0 : _a.call(props, e);
37
+ }
38
+ });
39
+ return () => {
40
+ controller.abort();
41
+ if (objectUrlRef.current) {
42
+ URL.revokeObjectURL(objectUrlRef.current);
43
+ }
44
+ };
45
+ }, []);
5
46
  // eslint-disable-next-line
6
47
  const onError = (e) => {
7
48
  var _a;
@@ -12,7 +53,7 @@ function ImgPreview(props) {
12
53
  var _a;
13
54
  (_a = props === null || props === void 0 ? void 0 : props.onSuccess) === null || _a === void 0 ? void 0 : _a.call(props);
14
55
  };
15
- return (jsx("div", { className: wrap, children: jsx("img", { src: props.filePath, onError: onError, onLoad: onLoad, className: img }) }));
56
+ return (jsx("div", { className: wrap, children: imgSrc && jsx("img", { src: imgSrc, onError: onError, onLoad: onLoad, className: img }) }));
16
57
  }
17
58
 
18
59
  export { ImgPreview as default };
@@ -43,7 +43,7 @@ function InnerFilePreview(props) {
43
43
  result = jsx(JsonPreview, Object.assign({}, props, { onError: onError, onSuccess: onSuccess }));
44
44
  }
45
45
  else if (filePath) {
46
- const ext = getFileExt(filePath).toLowerCase();
46
+ const ext = props.ext || getFileExt(filePath).toLowerCase();
47
47
  const spin = (jsx("div", { className: spinWrap, children: jsx(Spin, {}) }));
48
48
  let innerPreview = null;
49
49
  let canPreview = true;
@@ -83,7 +83,7 @@ function JsonPreview(props) {
83
83
  value = props.jsonConfig.data;
84
84
  }
85
85
  else {
86
- value = yield fetchFileWithAutoCode(props.filePath);
86
+ value = yield fetchFileWithAutoCode(props.filePath, props.httpHeaders);
87
87
  }
88
88
  try {
89
89
  const json = typeof value === 'string' ? JSON.parse(value) : value;
@@ -5,8 +5,9 @@ import ReactMarkdown from 'react-markdown';
5
5
  import rehypeRaw from 'rehype-raw';
6
6
  import remarkGfm from 'remark-gfm';
7
7
  import remarkMath from 'remark-math';
8
- import rehypeMathjax from 'rehype-mathjax';
9
8
  import { fetchFileWithAutoCode } from '../util.js';
9
+ import { typesetMathjax } from './load-mathjax.js';
10
+ import { remarkMathToText } from './rehype-math-to-text.js';
10
11
  import { wrap } from './index.module.scss.js';
11
12
 
12
13
  function processMarkdownImages(input) {
@@ -24,6 +25,18 @@ function processMarkdownImages(input) {
24
25
  }
25
26
  });
26
27
  }
28
+ /** 将 \(...\) 和 \[...\] 转换为 remark-math 能识别的 $/$$ 定界符 */
29
+ function normalizeMathDelimiters(input) {
30
+ return (input
31
+ // \[...\] → $$...$$ (块级公式,跨行)
32
+ .replace(/\\\[([\s\S]*?)\\\]/g, (_, inner) => {
33
+ return `$$\n${inner.trim()}\n$$`;
34
+ })
35
+ // \(...\) → $...$ (行内公式,单行)
36
+ .replace(/\\\(([^\n]*?)\\\)/g, (_, inner) => {
37
+ return `$${inner.trim()}$`;
38
+ }));
39
+ }
27
40
  function MDPreview(props) {
28
41
  const ref = useRef(null);
29
42
  const [content, setContent] = useState('');
@@ -32,10 +45,11 @@ function MDPreview(props) {
32
45
  // 测试vditor预览大小超过1MB的markdown文件非常耗时(超过10s)
33
46
  var _a, _b;
34
47
  try {
35
- let result = yield fetchFileWithAutoCode(props.filePath);
48
+ let result = yield fetchFileWithAutoCode(props.filePath, props.httpHeaders);
36
49
  // const start = new Date().getTime();
37
50
  // console.log('+++ start', start);
38
51
  result = processMarkdownImages(result);
52
+ result = normalizeMathDelimiters(result);
39
53
  // const end = new Date().getTime();
40
54
  // console.log('+++ end', end);
41
55
  // console.log(`processMarkdownImages time: ${end - start}ms`);
@@ -47,7 +61,12 @@ function MDPreview(props) {
47
61
  }
48
62
  }))();
49
63
  }, []);
50
- return (jsx("div", { ref: ref, style: { height: '100%', overflow: 'auto' }, className: wrap, children: content ? (jsx(ReactMarkdown, { className: "markdown-body", remarkPlugins: [remarkMath, remarkGfm], rehypePlugins: [rehypeRaw, rehypeMathjax], children: content })) : null }));
64
+ useEffect(() => {
65
+ if (content && ref.current) {
66
+ typesetMathjax(ref.current);
67
+ }
68
+ }, [content]);
69
+ return (jsx("div", { ref: ref, style: { height: '100%', overflow: 'auto' }, className: wrap, children: content ? (jsx(ReactMarkdown, { className: "markdown-body", remarkPlugins: [remarkMath, remarkGfm, remarkMathToText], rehypePlugins: [rehypeRaw], children: content })) : null }));
51
70
  }
52
71
 
53
72
  export { MDPreview as default };
@@ -0,0 +1,76 @@
1
+ import { __awaiter } from 'tslib';
2
+
3
+ let loaded = false;
4
+ let loading = null;
5
+ /**
6
+ * 从 CDN 加载全局 MathJax 脚本
7
+ * 配置支持 $...$ 和 $$...$$ 行内/块级公式
8
+ */
9
+ function loadMathjax() {
10
+ var _a;
11
+ // 宿主项目已自行配置并加载全局 MathJax,直接复用
12
+ if ((_a = window.MathJax) === null || _a === void 0 ? void 0 : _a.typesetPromise) {
13
+ loaded = true;
14
+ return Promise.resolve();
15
+ }
16
+ if (loaded)
17
+ return Promise.resolve();
18
+ if (loading)
19
+ return loading;
20
+ loading = new Promise((resolve, reject) => {
21
+ // 先写入 MathJax 配置
22
+ const scriptConfig = document.createElement('script');
23
+ scriptConfig.textContent = `
24
+ window.MathJax = {
25
+ tex: {
26
+ inlineMath: [['$', '$'], ['\\\\(', '\\\\)']],
27
+ displayMath: [['$$', '$$'], ['\\\\[', '\\\\]']],
28
+ processEscapes: true
29
+ },
30
+ options: {
31
+ enableMenu: false,
32
+ ignoreHtmlClass: 'no-mathjax',
33
+ processHtmlClass: 'mathjax-process'
34
+ },
35
+ startup: {
36
+ pageReady: function () {
37
+ return MathJax.startup.defaultPageReady();
38
+ }
39
+ }
40
+ };
41
+ `;
42
+ document.head.appendChild(scriptConfig);
43
+ // 加载 MathJax 核心脚本
44
+ const script = document.createElement('script');
45
+ script.src =
46
+ 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/npm/mathjax/3.2.2/es5/tex-mml-chtml.js';
47
+ script.async = true;
48
+ script.onload = () => {
49
+ loaded = true;
50
+ resolve();
51
+ };
52
+ script.onerror = () => reject(new Error('Failed to load MathJax from CDN'));
53
+ document.head.appendChild(script);
54
+ });
55
+ return loading;
56
+ }
57
+ /**
58
+ * 在指定容器内对 MathJax 公式重新排版
59
+ * 通常在 DOM 更新后调用
60
+ */
61
+ function typesetMathjax(container) {
62
+ return __awaiter(this, void 0, void 0, function* () {
63
+ var _a;
64
+ try {
65
+ yield loadMathjax();
66
+ if ((_a = window.MathJax) === null || _a === void 0 ? void 0 : _a.typesetPromise) {
67
+ yield window.MathJax.typesetPromise([container]);
68
+ }
69
+ }
70
+ catch (_b) {
71
+ // 静默失败,不阻塞渲染
72
+ }
73
+ });
74
+ }
75
+
76
+ export { loadMathjax, typesetMathjax };
@@ -0,0 +1,28 @@
1
+ import { visit } from 'unist-util-visit';
2
+
3
+ /**
4
+ * remark 插件:将 remark-math 解析的 math/inlineMath mdast 节点转回
5
+ * 带 $ 定界符的 text 节点,供全局 MathJax 后处理渲染
6
+ *
7
+ * 必须在 remark-rehype 转换之前执行,因为 mdast→hAST 阶段
8
+ * 无法识别 math 节点类型,会静默丢弃
9
+ */
10
+ function remarkMathToText() {
11
+ // eslint-disable-next-line
12
+ return (tree) => {
13
+ // eslint-disable-next-line
14
+ visit(tree, (node) => {
15
+ if (node.type === 'math' || node.type === 'inlineMath') {
16
+ const delimiter = node.type === 'inlineMath' ? '$' : '$$';
17
+ node.type = 'text';
18
+ node.value = `${delimiter}${node.value}${delimiter}`;
19
+ // 清除 remark-math 设置的 hAST 提示(hName, hProperties, hChildren)
20
+ // 否则 remark-rehype 仍会按 <code> 元素渲染,而不是纯文本
21
+ delete node.data;
22
+ delete node.meta;
23
+ }
24
+ });
25
+ };
26
+ }
27
+
28
+ export { remarkMathToText };
@@ -21,7 +21,7 @@ function ParquetPreview(props) {
21
21
  (() => __awaiter(this, void 0, void 0, function* () {
22
22
  var _a, _b, _c, _d;
23
23
  try {
24
- const resp = yield fetch(props.filePath);
24
+ const resp = yield fetch(props.filePath, { headers: props.httpHeaders });
25
25
  const parquetUint8Array = new Uint8Array(yield resp.arrayBuffer());
26
26
  // const start = new Date().getTime();
27
27
  ffiTable = readParquet(parquetUint8Array).intoFFI();
@@ -61,7 +61,7 @@ function roundToDivide(x, div) {
61
61
  return r === 0 ? x : Math.round(x - r + div);
62
62
  }
63
63
  function PdfPreview(props) {
64
- const { filePath } = props;
64
+ const { filePath, httpHeaders } = props;
65
65
  const pdfContainer = useRef(null);
66
66
  const pageSize = 3;
67
67
  const lastScrollTop = useRef(0);
@@ -192,6 +192,7 @@ function PdfPreview(props) {
192
192
  url: filePath,
193
193
  cMapUrl: 'https://haina-datahub.zero2x.org/ossRoute/frontend/resources/npm/pdfjs-dist/4.7.76/cmaps/',
194
194
  cMapPacked: true,
195
+ httpHeaders,
195
196
  })
196
197
  // eslint-disable-next-line
197
198
  .promise.then((doc) => {
@@ -9,7 +9,7 @@ function TxtPreview(props) {
9
9
  (() => __awaiter(this, void 0, void 0, function* () {
10
10
  var _a, _b;
11
11
  try {
12
- const result = yield fetchFileWithAutoCode(props.filePath);
12
+ const result = yield fetchFileWithAutoCode(props.filePath, props.httpHeaders);
13
13
  setContent(result);
14
14
  (_a = props === null || props === void 0 ? void 0 : props.onSuccess) === null || _a === void 0 ? void 0 : _a.call(props);
15
15
  }
@@ -59,9 +59,9 @@ function appendAsset(assetUrlArr) {
59
59
  * @param {string} fileUrl - 文件地址
60
60
  * @returns {Promise<string>} 解码后的正常字符串
61
61
  */
62
- function fetchFileWithAutoCode(fileUrl) {
62
+ function fetchFileWithAutoCode(fileUrl, httpHeaders) {
63
63
  return __awaiter(this, void 0, void 0, function* () {
64
- const response = yield fetch(fileUrl);
64
+ const response = yield fetch(fileUrl, { headers: httpHeaders });
65
65
  if (!response.ok)
66
66
  throw new Error(`请求失败:${response.status}`);
67
67
  const arrayBuffer = yield response.arrayBuffer();