dishui 0.0.84 → 0.0.89

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.
Files changed (89) hide show
  1. package/dist/components/Input/index.js +52 -62
  2. package/dist/components/Input/index.js.map +1 -1
  3. package/dist/components/LayoutSuite/index.d.ts +10 -0
  4. package/dist/components/LayoutSuite/index.js +27 -18
  5. package/dist/components/LayoutSuite/index.js.map +1 -1
  6. package/dist/components/Markdown/markdownComponents.js +1 -0
  7. package/dist/components/Markdown/markdownComponents.js.map +1 -1
  8. package/dist/components/Markdown/useMarkdownSource.js +46 -31
  9. package/dist/components/Markdown/useMarkdownSource.js.map +1 -1
  10. package/dist/components/Markdown/useMarkmapRenderer.js +44 -31
  11. package/dist/components/Markdown/useMarkmapRenderer.js.map +1 -1
  12. package/dist/components/Markdown/useMermaidRenderer.js +49 -31
  13. package/dist/components/Markdown/useMermaidRenderer.js.map +1 -1
  14. package/dist/components/ProtectedRoute/index.d.ts +2 -1
  15. package/dist/components/ProtectedRoute/index.js +16 -6
  16. package/dist/components/ProtectedRoute/index.js.map +1 -1
  17. package/dist/components/Select/index.d.ts +6 -0
  18. package/dist/components/Select/index.js +166 -136
  19. package/dist/components/Select/index.js.map +1 -1
  20. package/dist/components/Tabs/index.d.ts +12 -1
  21. package/dist/components/Tabs/index.js +42 -17
  22. package/dist/components/Tabs/index.js.map +1 -1
  23. package/dist/components/pro/ArticleContextMenu/index.js +7 -7
  24. package/dist/components/pro/CodeEditor/editors/CodeEditor.js +200 -189
  25. package/dist/components/pro/CodeEditor/editors/CodeEditor.js.map +1 -1
  26. package/dist/components/pro/CodeEditor/types.d.ts +3 -0
  27. package/dist/components/pro/DataTable/DataTable.d.ts +1 -1
  28. package/dist/components/pro/DataTable/DataTable.js +98 -240
  29. package/dist/components/pro/DataTable/DataTable.js.map +1 -1
  30. package/dist/components/pro/DataTable/components/TableBody.js +14 -16
  31. package/dist/components/pro/DataTable/components/TableBody.js.map +1 -1
  32. package/dist/components/pro/DataTable/components/TablePagination.d.ts +3 -9
  33. package/dist/components/pro/DataTable/components/TablePagination.js +29 -25
  34. package/dist/components/pro/DataTable/components/TablePagination.js.map +1 -1
  35. package/dist/components/pro/DataTable/tanstack-table.d.ts +2 -2
  36. package/dist/components/pro/DataTable/tanstack-table.js +29 -21
  37. package/dist/components/pro/DataTable/tanstack-table.js.map +1 -1
  38. package/dist/components/pro/DataTable/treeDataUtils.d.ts +2 -0
  39. package/dist/components/pro/DataTable/treeDataUtils.js +18 -19
  40. package/dist/components/pro/DataTable/treeDataUtils.js.map +1 -1
  41. package/dist/components/pro/DataTable/types.d.ts +24 -0
  42. package/dist/components/pro/DataTable/useDataTableModel.d.ts +16 -0
  43. package/dist/components/pro/DataTable/useDataTableModel.js +148 -0
  44. package/dist/components/pro/DataTable/useDataTableModel.js.map +1 -0
  45. package/dist/components/pro/FileExplorer/Breadcrumb.js +12 -12
  46. package/dist/components/pro/Flow/nodes/DataNodeView.js +87 -82
  47. package/dist/components/pro/Flow/nodes/DataNodeView.js.map +1 -1
  48. package/dist/components/pro/Flow/nodes/MediaContent.js +1 -1
  49. package/dist/components/pro/Flow/nodes/MediaContent.js.map +1 -1
  50. package/dist/components/pro/Flow/nodes/MediaOverlay.js +1 -1
  51. package/dist/components/pro/Flow/nodes/MediaOverlay.js.map +1 -1
  52. package/dist/components/pro/Flow/nodes/Model3DView.js +69 -63
  53. package/dist/components/pro/Flow/nodes/Model3DView.js.map +1 -1
  54. package/dist/components/pro/Flow/nodes/chartGeometryBudget.d.ts +14 -0
  55. package/dist/components/pro/Flow/nodes/chartGeometryBudget.js +76 -0
  56. package/dist/components/pro/Flow/nodes/chartGeometryBudget.js.map +1 -0
  57. package/dist/components/pro/Flow/nodes/demandRender.d.ts +11 -0
  58. package/dist/components/pro/Flow/nodes/demandRender.js +26 -0
  59. package/dist/components/pro/Flow/nodes/demandRender.js.map +1 -0
  60. package/dist/components/pro/RichTextEditor/SlashMenu.js +68 -58
  61. package/dist/components/pro/RichTextEditor/SlashMenu.js.map +1 -1
  62. package/dist/contexts/AppContext.d.ts +1 -0
  63. package/dist/contexts/AppContext.js +144 -145
  64. package/dist/contexts/AppContext.js.map +1 -1
  65. package/dist/contexts/authentication.d.ts +7 -0
  66. package/dist/contexts/authentication.js +22 -0
  67. package/dist/contexts/authentication.js.map +1 -0
  68. package/dist/dishui.css +1 -1
  69. package/dist/hooks/useControllableState.d.ts +6 -14
  70. package/dist/hooks/useControllableState.js +21 -15
  71. package/dist/hooks/useControllableState.js.map +1 -1
  72. package/dist/hooks/useTableColumns.d.ts +3 -3
  73. package/dist/hooks/useTableColumns.js +2 -2
  74. package/dist/hooks/useTableColumns.js.map +1 -1
  75. package/dist/hooks/useTableData.d.ts +11 -4
  76. package/dist/hooks/useTableData.js +44 -26
  77. package/dist/hooks/useTableData.js.map +1 -1
  78. package/dist/index-nostyled.d.ts +2 -2
  79. package/dist/index.d.ts +2 -2
  80. package/dist/index.js +97 -97
  81. package/dist/utils/i18n.js +1 -1
  82. package/dist/utils/i18n.js.map +1 -1
  83. package/dist/utils/routerBuilder.d.ts +7 -5
  84. package/dist/utils/routerBuilder.js +11 -15
  85. package/dist/utils/routerBuilder.js.map +1 -1
  86. package/dist/utils/safeStorage.d.ts +8 -0
  87. package/dist/utils/safeStorage.js +45 -0
  88. package/dist/utils/safeStorage.js.map +1 -0
  89. package/package.json +22 -5
@@ -1,50 +1,65 @@
1
- import { useEffect as e, useState as t } from "react";
1
+ import { useEffect as e, useRef as t, useState as n } from "react";
2
2
  //#region src/components/Markdown/useMarkdownSource.ts
3
- async function n(e, t) {
4
- let n = /^s3:\/\/([^/]+)\/(.+)$/.exec(e);
5
- if (!n) throw Error("Invalid S3 URL format. Expected: s3://bucket/key");
6
- let [, r, i] = n, a = t?.region || "us-east-1", o = `${t?.endpoint || `https://${r}.s3.${a}.amazonaws.com`}/${i}`, s = await fetch(o);
7
- if (!s.ok) throw Error(`S3 fetch failed: ${s.status} ${s.statusText}`);
8
- return s.text();
3
+ async function r(e, t, n) {
4
+ let r = /^s3:\/\/([^/]+)\/(.+)$/.exec(e);
5
+ if (!r) throw Error("Invalid S3 URL format. Expected: s3://bucket/key");
6
+ let [, i, a] = r, o = t?.region || "us-east-1", s = `${t?.endpoint || `https://${i}.s3.${o}.amazonaws.com`}/${a}`, c = await fetch(s, { signal: n });
7
+ if (!c.ok) throw Error(`S3 fetch failed: ${c.status} ${c.statusText}`);
8
+ return c.text();
9
9
  }
10
- function r(r, i, a, o, s) {
11
- let [c, l] = t(""), [u, d] = t(!1), [f, p] = t(null);
10
+ function i(i, a, o, s, c) {
11
+ let [l, u] = n(""), [d, f] = n(!1), [p, m] = n(null), h = t({
12
+ onLoad: s,
13
+ onError: c
14
+ });
15
+ e(() => {
16
+ h.current = {
17
+ onLoad: s,
18
+ onError: c
19
+ };
20
+ }, [s, c]);
21
+ let g = o?.region, _ = o?.endpoint;
12
22
  return e(() => {
13
- (async () => {
14
- if (r === "text") {
15
- l(i), o?.(i);
23
+ let e = new AbortController(), { signal: t } = e;
24
+ return (async () => {
25
+ if (m(null), i === "text") {
26
+ u(a), f(!1), h.current.onLoad?.(a);
16
27
  return;
17
28
  }
18
- d(!0), p(null);
29
+ f(!0), u("");
19
30
  try {
20
31
  let e;
21
- if (r === "url") {
22
- let t = await fetch(i);
23
- if (!t.ok) throw Error(`HTTP error! status: ${t.status}`);
24
- e = await t.text();
25
- } else if (r === "s3") e = await n(i, a);
26
- else throw Error(`Unsupported source type: ${r}`);
27
- l(e), o?.(e);
32
+ if (i === "url") {
33
+ let n = await fetch(a, { signal: t });
34
+ if (!n.ok) throw Error(`HTTP error! status: ${n.status}`);
35
+ e = await n.text();
36
+ } else if (i === "s3") e = await r(a, {
37
+ region: g,
38
+ endpoint: _
39
+ }, t);
40
+ else throw Error(`Unsupported source type: ${i}`);
41
+ if (t.aborted) return;
42
+ u(e), h.current.onLoad?.(e);
28
43
  } catch (e) {
29
- let t = e instanceof Error ? e : Error(String(e));
30
- p(t), s?.(t);
44
+ if (t.aborted) return;
45
+ let n = e instanceof Error ? e : Error(String(e));
46
+ m(n), h.current.onError?.(n);
31
47
  } finally {
32
- d(!1);
48
+ t.aborted || f(!1);
33
49
  }
34
- })();
50
+ })(), () => e.abort();
35
51
  }, [
36
- r,
37
52
  i,
38
53
  a,
39
- o,
40
- s
54
+ g,
55
+ _
41
56
  ]), {
42
- markdown: c,
43
- loading: u,
44
- error: f
57
+ markdown: l,
58
+ loading: d,
59
+ error: p
45
60
  };
46
61
  }
47
62
  //#endregion
48
- export { r as useMarkdownSource };
63
+ export { i as useMarkdownSource };
49
64
 
50
65
  //# sourceMappingURL=useMarkdownSource.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMarkdownSource.js","names":[],"sources":["../../../src/components/Markdown/useMarkdownSource.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport type { MarkdownSource, MarkdownProps } from './Markdown';\n\nasync function fetchFromS3(\n s3Url: string,\n config?: MarkdownProps['s3Config']\n): Promise<string> {\n const match = /^s3:\\/\\/([^/]+)\\/(.+)$/.exec(s3Url);\n if (!match) {\n throw new Error('Invalid S3 URL format. Expected: s3://bucket/key');\n }\n\n const [, bucket, key] = match;\n const region = config?.region || 'us-east-1';\n const endpoint = config?.endpoint || `https://${bucket}.s3.${region}.amazonaws.com`;\n const url = `${endpoint}/${key}`;\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`S3 fetch failed: ${response.status} ${response.statusText}`);\n }\n\n return response.text();\n}\n\nexport function useMarkdownSource(\n source: MarkdownSource,\n content: string,\n s3Config?: MarkdownProps['s3Config'],\n onLoad?: (content: string) => void,\n onError?: (error: Error) => void,\n) {\n const [markdown, setMarkdown] = useState<string>('');\n const [loading, setLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n const fetchContent = async () => {\n if (source === 'text') {\n setMarkdown(content);\n onLoad?.(content);\n return;\n }\n\n setLoading(true);\n setError(null);\n\n try {\n let text: string;\n\n if (source === 'url') {\n const response = await fetch(content);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n text = await response.text();\n } else if (source === 's3') {\n text = await fetchFromS3(content, s3Config);\n } else {\n throw new Error(`Unsupported source type: ${source}`);\n }\n\n setMarkdown(text);\n onLoad?.(text);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onError?.(error);\n } finally {\n setLoading(false);\n }\n };\n\n fetchContent();\n }, [source, content, s3Config, onLoad, onError]);\n\n return { markdown, loading, error };\n}\n"],"mappings":";;AAGA,eAAe,EACb,GACA,GACiB;CACjB,IAAM,IAAQ,yBAAyB,KAAK,CAAK;CACjD,IAAI,CAAC,GACH,MAAU,MAAM,kDAAkD;CAGpE,IAAM,GAAG,GAAQ,KAAO,GAClB,IAAS,GAAQ,UAAU,aAE3B,IAAM,GADK,GAAQ,YAAY,WAAW,EAAO,MAAM,EAAO,gBAC5C,GAAG,KAErB,IAAW,MAAM,MAAM,CAAG;CAChC,IAAI,CAAC,EAAS,IACZ,MAAU,MAAM,oBAAoB,EAAS,OAAO,GAAG,EAAS,YAAY;CAG9E,OAAO,EAAS,KAAK;AACvB;AAEA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA;CACA,IAAM,CAAC,GAAU,KAAe,EAAiB,EAAE,GAC7C,CAAC,GAAS,KAAc,EAAkB,EAAK,GAC/C,CAAC,GAAO,KAAY,EAAuB,IAAI;CA0CrD,OAxCA,QAAgB;EAqCd,aApCiC;GAC/B,IAAI,MAAW,QAAQ;IAErB,AADA,EAAY,CAAO,GACnB,IAAS,CAAO;IAChB;GACF;GAGA,AADA,EAAW,EAAI,GACf,EAAS,IAAI;GAEb,IAAI;IACF,IAAI;IAEJ,IAAI,MAAW,OAAO;KACpB,IAAM,IAAW,MAAM,MAAM,CAAO;KACpC,IAAI,CAAC,EAAS,IACZ,MAAU,MAAM,uBAAuB,EAAS,QAAQ;KAE1D,IAAO,MAAM,EAAS,KAAK;IAC7B,OAAO,IAAI,MAAW,MACpB,IAAO,MAAM,EAAY,GAAS,CAAQ;SAE1C,MAAU,MAAM,4BAA4B,GAAQ;IAItD,AADA,EAAY,CAAI,GAChB,IAAS,CAAI;GACf,SAAS,GAAK;IACZ,IAAM,IAAQ,aAAe,QAAQ,IAAU,MAAM,OAAO,CAAG,CAAC;IAEhE,AADA,EAAS,CAAK,GACd,IAAU,CAAK;GACjB,UAAU;IACR,EAAW,EAAK;GAClB;EACF,EAEA,CAAa;CACf,GAAG;EAAC;EAAQ;EAAS;EAAU;EAAQ;CAAO,CAAC,GAExC;EAAE;EAAU;EAAS;CAAM;AACpC"}
1
+ {"version":3,"file":"useMarkdownSource.js","names":[],"sources":["../../../src/components/Markdown/useMarkdownSource.ts"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport type { MarkdownSource, MarkdownProps } from './Markdown';\n\nasync function fetchFromS3(\n s3Url: string,\n config?: MarkdownProps['s3Config'],\n signal?: AbortSignal,\n): Promise<string> {\n const match = /^s3:\\/\\/([^/]+)\\/(.+)$/.exec(s3Url);\n if (!match) {\n throw new Error('Invalid S3 URL format. Expected: s3://bucket/key');\n }\n\n const [, bucket, key] = match;\n const region = config?.region || 'us-east-1';\n const endpoint = config?.endpoint || `https://${bucket}.s3.${region}.amazonaws.com`;\n const url = `${endpoint}/${key}`;\n\n const response = await fetch(url, { signal });\n if (!response.ok) {\n throw new Error(`S3 fetch failed: ${response.status} ${response.statusText}`);\n }\n\n return response.text();\n}\n\nexport function useMarkdownSource(\n source: MarkdownSource,\n content: string,\n s3Config?: MarkdownProps['s3Config'],\n onLoad?: (content: string) => void,\n onError?: (error: Error) => void,\n) {\n const [markdown, setMarkdown] = useState<string>('');\n const [loading, setLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Callback identity is not a source change; notify the latest committed callbacks.\n const callbacks = useRef({ onLoad, onError });\n useEffect(() => {\n callbacks.current = { onLoad, onError };\n }, [onLoad, onError]);\n\n const region = s3Config?.region;\n const endpoint = s3Config?.endpoint;\n\n useEffect(() => {\n const controller = new AbortController();\n const { signal } = controller;\n\n const fetchContent = async () => {\n setError(null);\n if (source === 'text') {\n setMarkdown(content);\n setLoading(false);\n callbacks.current.onLoad?.(content);\n return;\n }\n\n setLoading(true);\n setMarkdown('');\n\n try {\n let text: string;\n\n if (source === 'url') {\n const response = await fetch(content, { signal });\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n text = await response.text();\n } else if (source === 's3') {\n text = await fetchFromS3(content, { region, endpoint }, signal);\n } else {\n throw new Error(`Unsupported source type: ${source}`);\n }\n\n if (signal.aborted) return;\n setMarkdown(text);\n callbacks.current.onLoad?.(text);\n } catch (err) {\n if (signal.aborted) return;\n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n callbacks.current.onError?.(error);\n } finally {\n if (!signal.aborted) setLoading(false);\n }\n };\n\n fetchContent();\n return () => controller.abort();\n }, [source, content, region, endpoint]);\n\n return { markdown, loading, error };\n}\n"],"mappings":";;AAGA,eAAe,EACb,GACA,GACA,GACiB;CACjB,IAAM,IAAQ,yBAAyB,KAAK,CAAK;CACjD,IAAI,CAAC,GACH,MAAU,MAAM,kDAAkD;CAGpE,IAAM,GAAG,GAAQ,KAAO,GAClB,IAAS,GAAQ,UAAU,aAE3B,IAAM,GADK,GAAQ,YAAY,WAAW,EAAO,MAAM,EAAO,gBAC5C,GAAG,KAErB,IAAW,MAAM,MAAM,GAAK,EAAE,UAAO,CAAC;CAC5C,IAAI,CAAC,EAAS,IACZ,MAAU,MAAM,oBAAoB,EAAS,OAAO,GAAG,EAAS,YAAY;CAG9E,OAAO,EAAS,KAAK;AACvB;AAEA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA;CACA,IAAM,CAAC,GAAU,KAAe,EAAiB,EAAE,GAC7C,CAAC,GAAS,KAAc,EAAkB,EAAK,GAC/C,CAAC,GAAO,KAAY,EAAuB,IAAI,GAG/C,IAAY,EAAO;EAAE;EAAQ;CAAQ,CAAC;CAC5C,QAAgB;EACd,EAAU,UAAU;GAAE;GAAQ;EAAQ;CACxC,GAAG,CAAC,GAAQ,CAAO,CAAC;CAEpB,IAAM,IAAS,GAAU,QACnB,IAAW,GAAU;CAkD3B,OAhDA,QAAgB;EACd,IAAM,IAAa,IAAI,gBAAgB,GACjC,EAAE,cAAW;EA2CnB,QADA,YAxCiC;GAE/B,IADA,EAAS,IAAI,GACT,MAAW,QAAQ;IAGrB,AAFA,EAAY,CAAO,GACnB,EAAW,EAAK,GAChB,EAAU,QAAQ,SAAS,CAAO;IAClC;GACF;GAGA,AADA,EAAW,EAAI,GACf,EAAY,EAAE;GAEd,IAAI;IACF,IAAI;IAEJ,IAAI,MAAW,OAAO;KACpB,IAAM,IAAW,MAAM,MAAM,GAAS,EAAE,UAAO,CAAC;KAChD,IAAI,CAAC,EAAS,IACZ,MAAU,MAAM,uBAAuB,EAAS,QAAQ;KAE1D,IAAO,MAAM,EAAS,KAAK;IAC7B,OAAO,IAAI,MAAW,MACpB,IAAO,MAAM,EAAY,GAAS;KAAE;KAAQ;IAAS,GAAG,CAAM;SAE9D,MAAU,MAAM,4BAA4B,GAAQ;IAGtD,IAAI,EAAO,SAAS;IAEpB,AADA,EAAY,CAAI,GAChB,EAAU,QAAQ,SAAS,CAAI;GACjC,SAAS,GAAK;IACZ,IAAI,EAAO,SAAS;IACpB,IAAM,IAAQ,aAAe,QAAQ,IAAU,MAAM,OAAO,CAAG,CAAC;IAEhE,AADA,EAAS,CAAK,GACd,EAAU,QAAQ,UAAU,CAAK;GACnC,UAAU;IACR,AAAK,EAAO,WAAS,EAAW,EAAK;GACvC;EACF,EAEA,CAAa,SACA,EAAW,MAAM;CAChC,GAAG;EAAC;EAAQ;EAAS;EAAQ;CAAQ,CAAC,GAE/B;EAAE;EAAU;EAAS;CAAM;AACpC"}
@@ -1,38 +1,51 @@
1
- import { useEffect as e } from "react";
2
- import { Transformer as t } from "markmap-lib";
3
- import { Markmap as n } from "markmap-view";
1
+ import { useEffect as e, useRef as t } from "react";
2
+ import { Transformer as n } from "markmap-lib";
3
+ import { Markmap as r } from "markmap-view";
4
4
  //#region src/components/Markdown/useMarkmapRenderer.ts
5
- function r(r, i, a) {
5
+ function i(e, t) {
6
+ clearTimeout(t.timer);
7
+ let n = t.instance;
8
+ t.instance = void 0, n && (n.svg.interrupt(), n.svg.selectAll("*").interrupt(), n.svg.on("wheel", null), n.destroy()), t.output && e.firstChild === t.output && e.replaceChildren();
9
+ }
10
+ function a(a, o, s) {
11
+ let c = t(/* @__PURE__ */ new Map()), l = t(null);
6
12
  e(() => {
7
- a && i && r.current && setTimeout(() => {
8
- try {
9
- let e = r.current?.querySelectorAll(".markmap-diagram");
10
- if (e) {
11
- let r = new t();
12
- for (let t = 0; t < e.length; t++) {
13
- let i = e[t], a = i.getAttribute("data-markmap-content") || "";
14
- if (a.trim()) try {
15
- i.innerHTML = "";
16
- let e = document.createElementNS("http://www.w3.org/2000/svg", "svg");
17
- e.style.width = "100%", e.style.height = "500px", i.appendChild(e);
18
- let { root: t } = r.transform(a);
19
- n.create(e, {}, t);
20
- } catch (e) {
21
- console.error("Markmap render error:", e), i.innerHTML = `<pre class="text-error">Markmap 渲染失败: ${e}</pre>`;
22
- }
23
- }
13
+ let e = c.current;
14
+ return () => {
15
+ e.forEach((e, t) => i(t, e)), e.clear();
16
+ };
17
+ }, []), e(() => {
18
+ let e = c.current, t = a.current, u = new Set(s && o && t ? t.querySelectorAll(".markmap-diagram") : []);
19
+ e.forEach((t, n) => {
20
+ (!u.has(n) || n.getAttribute("data-markmap-content") !== t.code || t.output && n.firstChild !== t.output) && (e.delete(n), i(n, t));
21
+ }), u.forEach((o) => {
22
+ if (e.has(o)) return;
23
+ let s = o.getAttribute("data-markmap-content") || "";
24
+ if (!s.trim()) return;
25
+ let c = {
26
+ code: s,
27
+ output: null
28
+ };
29
+ e.set(o, c);
30
+ let u = () => e.get(o) === c && a.current === t && t?.contains(o) && o.getAttribute("data-markmap-content") === s && (!c.output || o.firstChild === c.output);
31
+ c.timer = setTimeout(async () => {
32
+ if (u()) try {
33
+ l.current ??= new n();
34
+ let { root: e } = l.current.transform(s), t = document.createElementNS("http://www.w3.org/2000/svg", "svg");
35
+ t.style.width = "100%", t.style.height = "500px", o.replaceChildren(t), c.output = t;
36
+ let i = r.create(t, {});
37
+ c.instance = i, await i.setData(e), u() && await i.fit();
38
+ } catch (e) {
39
+ if (!u()) return;
40
+ i(o, c), console.error("Markmap render error:", e);
41
+ let t = document.createElement("pre");
42
+ t.className = "text-error", t.textContent = `Markmap 渲染失败: ${e}`, o.replaceChildren(t), c.output = t;
24
43
  }
25
- } catch (e) {
26
- console.error("Markmap rendering failed:", e);
27
- }
28
- }, 100);
29
- }, [
30
- i,
31
- a,
32
- r
33
- ]);
44
+ }, 100);
45
+ });
46
+ });
34
47
  }
35
48
  //#endregion
36
- export { r as useMarkmapRenderer };
49
+ export { a as useMarkmapRenderer };
37
50
 
38
51
  //# sourceMappingURL=useMarkmapRenderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMarkmapRenderer.js","names":[],"sources":["../../../src/components/Markdown/useMarkmapRenderer.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { Transformer } from 'markmap-lib';\nimport { Markmap } from 'markmap-view';\n\nexport function useMarkmapRenderer(\n containerRef: React.RefObject<HTMLDivElement | null>,\n markdown: string,\n enabled: boolean,\n) {\n useEffect(() => {\n if (!enabled || !markdown || !containerRef.current) return;\n\n const renderMarkmap = () => {\n try {\n const markmapElements = containerRef.current?.querySelectorAll('.markmap-diagram');\n if (markmapElements) {\n const transformer = new Transformer();\n\n for (let i = 0; i < markmapElements.length; i++) {\n const element = markmapElements[i] as HTMLElement;\n const code = element.getAttribute('data-markmap-content') || '';\n\n if (code.trim()) {\n try {\n element.innerHTML = '';\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.style.width = '100%';\n svg.style.height = '500px';\n element.appendChild(svg);\n\n const { root } = transformer.transform(code);\n Markmap.create(svg, {}, root);\n } catch (err) {\n console.error('Markmap render error:', err);\n element.innerHTML = `<pre class=\"text-error\">Markmap 渲染失败: ${err}</pre>`;\n }\n }\n }\n }\n } catch (err) {\n console.error('Markmap rendering failed:', err);\n }\n };\n\n setTimeout(renderMarkmap, 100);\n }, [markdown, enabled, containerRef]);\n}\n"],"mappings":";;;;AAIA,SAAgB,EACd,GACA,GACA,GACA;CACA,QAAgB;EACV,AAAC,KAAY,KAAa,EAAa,WAkC3C,iBAhC4B;GAC1B,IAAI;IACF,IAAM,IAAkB,EAAa,SAAS,iBAAiB,kBAAkB;IACjF,IAAI,GAAiB;KACnB,IAAM,IAAc,IAAI,EAAY;KAEpC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAgB,QAAQ,KAAK;MAC/C,IAAM,IAAU,EAAgB,IAC1B,IAAO,EAAQ,aAAa,sBAAsB,KAAK;MAE7D,IAAI,EAAK,KAAK,GACZ,IAAI;OACF,EAAQ,YAAY;OACpB,IAAM,IAAM,SAAS,gBAAgB,8BAA8B,KAAK;OAGxE,AAFA,EAAI,MAAM,QAAQ,QAClB,EAAI,MAAM,SAAS,SACnB,EAAQ,YAAY,CAAG;OAEvB,IAAM,EAAE,YAAS,EAAY,UAAU,CAAI;OAC3C,EAAQ,OAAO,GAAK,CAAC,GAAG,CAAI;MAC9B,SAAS,GAAK;OAEZ,AADA,QAAQ,MAAM,yBAAyB,CAAG,GAC1C,EAAQ,YAAY,yCAAyC,EAAI;MACnE;KAEJ;IACF;GACF,SAAS,GAAK;IACZ,QAAQ,MAAM,6BAA6B,CAAG;GAChD;EACF,GAE0B,GAAG;CAC/B,GAAG;EAAC;EAAU;EAAS;CAAY,CAAC;AACtC"}
1
+ {"version":3,"file":"useMarkmapRenderer.js","names":[],"sources":["../../../src/components/Markdown/useMarkmapRenderer.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { Transformer } from 'markmap-lib';\nimport { Markmap } from 'markmap-view';\n\ninterface Diagram {\n code: string;\n output: ChildNode | null;\n instance?: Markmap;\n timer?: ReturnType<typeof setTimeout>;\n}\n\nfunction dispose(element: HTMLElement, diagram: Diagram) {\n clearTimeout(diagram.timer);\n const instance = diagram.instance;\n diagram.instance = undefined;\n if (instance) {\n // destroy disconnects Markmap's observer/refresh subscription. Stop D3's\n // transitions and pan listener too, before removing the owned SVG.\n instance.svg.interrupt();\n instance.svg.selectAll('*').interrupt();\n instance.svg.on('wheel', null);\n instance.destroy();\n }\n if (diagram.output && element.firstChild === diagram.output) {\n element.replaceChildren();\n }\n}\n\nexport function useMarkmapRenderer(\n containerRef: React.RefObject<HTMLDivElement | null>,\n markdown: string,\n enabled: boolean,\n) {\n const diagrams = useRef(new Map<HTMLElement, Diagram>());\n const transformer = useRef<Transformer | null>(null);\n\n useEffect(() => {\n const entries = diagrams.current;\n return () => {\n entries.forEach((diagram, element) => dispose(element, diagram));\n entries.clear();\n };\n }, []);\n\n useEffect(() => {\n const entries = diagrams.current;\n const container = containerRef.current;\n const elements = new Set(enabled && markdown && container\n ? container.querySelectorAll<HTMLElement>('.markmap-diagram') : []);\n\n entries.forEach((diagram, element) => {\n if (!elements.has(element) || element.getAttribute('data-markmap-content') !== diagram.code ||\n (diagram.output && element.firstChild !== diagram.output)) {\n entries.delete(element);\n dispose(element, diagram);\n }\n });\n\n elements.forEach(element => {\n if (entries.has(element)) return;\n const code = element.getAttribute('data-markmap-content') || '';\n if (!code.trim()) return;\n const diagram: Diagram = { code, output: null };\n entries.set(element, diagram);\n const canCommit = () => entries.get(element) === diagram &&\n containerRef.current === container && container?.contains(element) &&\n element.getAttribute('data-markmap-content') === code &&\n (!diagram.output || element.firstChild === diagram.output);\n\n diagram.timer = setTimeout(async () => {\n if (!canCommit()) return;\n try {\n transformer.current ??= new Transformer();\n const { root } = transformer.current.transform(code);\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.style.width = '100%';\n svg.style.height = '500px';\n element.replaceChildren(svg);\n diagram.output = svg;\n // Keep the instance before starting asynchronous layout, so cleanup\n // can destroy it and a superseded layout never starts a late fit.\n const instance = Markmap.create(svg, {});\n diagram.instance = instance;\n await instance.setData(root);\n if (canCommit()) await instance.fit();\n } catch (err) {\n if (!canCommit()) return;\n dispose(element, diagram);\n console.error('Markmap render error:', err);\n const fallback = document.createElement('pre');\n fallback.className = 'text-error';\n fallback.textContent = `Markmap 渲染失败: ${err}`;\n element.replaceChildren(fallback);\n diagram.output = fallback;\n }\n }, 100);\n });\n });\n}\n"],"mappings":";;;;AAWA,SAAS,EAAQ,GAAsB,GAAkB;CACvD,aAAa,EAAQ,KAAK;CAC1B,IAAM,IAAW,EAAQ;CAUzB,AATA,EAAQ,WAAW,KAAA,GACf,MAGF,EAAS,IAAI,UAAU,GACvB,EAAS,IAAI,UAAU,GAAG,CAAC,CAAC,UAAU,GACtC,EAAS,IAAI,GAAG,SAAS,IAAI,GAC7B,EAAS,QAAQ,IAEf,EAAQ,UAAU,EAAQ,eAAe,EAAQ,UACnD,EAAQ,gBAAgB;AAE5B;AAEA,SAAgB,EACd,GACA,GACA,GACA;CACA,IAAM,IAAW,kBAAO,IAAI,IAA0B,CAAC,GACjD,IAAc,EAA2B,IAAI;CAUnD,AARA,QAAgB;EACd,IAAM,IAAU,EAAS;EACzB,aAAa;GAEX,AADA,EAAQ,SAAS,GAAS,MAAY,EAAQ,GAAS,CAAO,CAAC,GAC/D,EAAQ,MAAM;EAChB;CACF,GAAG,CAAC,CAAC,GAEL,QAAgB;EACd,IAAM,IAAU,EAAS,SACnB,IAAY,EAAa,SACzB,IAAW,IAAI,IAAI,KAAW,KAAY,IAC5C,EAAU,iBAA8B,kBAAkB,IAAI,CAAC,CAAC;EAUpE,AARA,EAAQ,SAAS,GAAS,MAAY;GACpC,CAAI,CAAC,EAAS,IAAI,CAAO,KAAK,EAAQ,aAAa,sBAAsB,MAAM,EAAQ,QAClF,EAAQ,UAAU,EAAQ,eAAe,EAAQ,YACpD,EAAQ,OAAO,CAAO,GACtB,EAAQ,GAAS,CAAO;EAE5B,CAAC,GAED,EAAS,SAAQ,MAAW;GAC1B,IAAI,EAAQ,IAAI,CAAO,GAAG;GAC1B,IAAM,IAAO,EAAQ,aAAa,sBAAsB,KAAK;GAC7D,IAAI,CAAC,EAAK,KAAK,GAAG;GAClB,IAAM,IAAmB;IAAE;IAAM,QAAQ;GAAK;GAC9C,EAAQ,IAAI,GAAS,CAAO;GAC5B,IAAM,UAAkB,EAAQ,IAAI,CAAO,MAAM,KAC/C,EAAa,YAAY,KAAa,GAAW,SAAS,CAAO,KACjE,EAAQ,aAAa,sBAAsB,MAAM,MAChD,CAAC,EAAQ,UAAU,EAAQ,eAAe,EAAQ;GAErD,EAAQ,QAAQ,WAAW,YAAY;IAChC,MAAU,GACf,IAAI;KACF,EAAY,YAAY,IAAI,EAAY;KACxC,IAAM,EAAE,YAAS,EAAY,QAAQ,UAAU,CAAI,GAC7C,IAAM,SAAS,gBAAgB,8BAA8B,KAAK;KAIxE,AAHA,EAAI,MAAM,QAAQ,QAClB,EAAI,MAAM,SAAS,SACnB,EAAQ,gBAAgB,CAAG,GAC3B,EAAQ,SAAS;KAGjB,IAAM,IAAW,EAAQ,OAAO,GAAK,CAAC,CAAC;KAGvC,AAFA,EAAQ,WAAW,GACnB,MAAM,EAAS,QAAQ,CAAI,GACvB,EAAU,KAAG,MAAM,EAAS,IAAI;IACtC,SAAS,GAAK;KACZ,IAAI,CAAC,EAAU,GAAG;KAElB,AADA,EAAQ,GAAS,CAAO,GACxB,QAAQ,MAAM,yBAAyB,CAAG;KAC1C,IAAM,IAAW,SAAS,cAAc,KAAK;KAI7C,AAHA,EAAS,YAAY,cACrB,EAAS,cAAc,iBAAiB,KACxC,EAAQ,gBAAgB,CAAQ,GAChC,EAAQ,SAAS;IACnB;GACF,GAAG,GAAG;EACR,CAAC;CACH,CAAC;AACH"}
@@ -1,44 +1,62 @@
1
- import { useEffect as e, useRef as t, useState as n } from "react";
2
- import r from "mermaid";
1
+ import { useEffect as e, useRef as t } from "react";
2
+ import n from "mermaid";
3
3
  //#region src/components/Markdown/useMermaidRenderer.ts
4
- function i(i, a, o) {
5
- let [s, c] = n(!1), l = t(!1);
4
+ var r = 0;
5
+ function i(e, t) {
6
+ let n = e.getAttribute("data-mermaid-content");
7
+ return n === null ? t?.output && e.firstChild === t.output ? t.code : e.querySelector("svg") ? "" : e.textContent || "" : n;
8
+ }
9
+ function a(e, t) {
10
+ clearTimeout(t.timer), t.output && e.firstChild === t.output && (e.textContent = i(e, t));
11
+ }
12
+ function o(o, s, c) {
13
+ let l = t(!1), u = t(/* @__PURE__ */ new Map());
6
14
  e(() => {
7
- o && !l.current && (r.initialize({
15
+ let e = u.current;
16
+ return () => {
17
+ e.forEach((e, t) => a(t, e)), e.clear();
18
+ };
19
+ }, []), e(() => {
20
+ c && !l.current && (n.initialize({
8
21
  startOnLoad: !1,
9
22
  theme: "default",
10
- securityLevel: "loose",
23
+ securityLevel: "strict",
24
+ htmlLabels: !1,
11
25
  fontFamily: "inherit",
12
26
  flowchart: {
13
27
  useMaxWidth: !0,
14
- htmlLabels: !0
28
+ htmlLabels: !1
15
29
  }
16
- }), l.current = !0, c(!0));
17
- }, [o]), e(() => {
18
- o && s && a && i.current && setTimeout(async () => {
19
- try {
20
- let e = i.current?.querySelectorAll(".mermaid-diagram");
21
- if (e) for (let t = 0; t < e.length; t++) {
22
- let n = e[t], i = n.textContent || "";
23
- if (i.trim()) try {
24
- let { svg: e } = await r.render(`mermaid-${Date.now()}-${t}`, i);
25
- n.innerHTML = e;
26
- } catch (e) {
27
- console.error("Mermaid render error:", e), n.innerHTML = `<pre class="text-error">${i}</pre>`;
28
- }
30
+ }), l.current = !0);
31
+ let e = u.current, t = o.current, d = new Set(c && s && t ? t.querySelectorAll(".mermaid-diagram") : []);
32
+ e.forEach((t, n) => {
33
+ (!d.has(n) || i(n, t) !== t.code || t.output && n.firstChild !== t.output) && (e.delete(n), a(n, t));
34
+ }), d.forEach((a) => {
35
+ if (e.has(a)) return;
36
+ let s = i(a);
37
+ if (!s.trim()) return;
38
+ let c = {
39
+ code: s,
40
+ output: null
41
+ };
42
+ e.set(a, c);
43
+ let l = () => e.get(a) === c && o.current === t && t?.contains(a) && i(a, c) === s;
44
+ c.timer = setTimeout(async () => {
45
+ if (l()) try {
46
+ let { svg: e } = await n.render(`dishui-mermaid-${++r}`, s);
47
+ if (!l()) return;
48
+ a.innerHTML = e, c.output = a.firstChild;
49
+ } catch (e) {
50
+ if (!l()) return;
51
+ console.error("Mermaid render error:", e);
52
+ let t = document.createElement("pre");
53
+ t.className = "text-error", t.textContent = s, a.replaceChildren(t), c.output = t;
29
54
  }
30
- } catch (e) {
31
- console.error("Mermaid rendering failed:", e);
32
- }
33
- }, 100);
34
- }, [
35
- a,
36
- o,
37
- s,
38
- i
39
- ]);
55
+ }, 100);
56
+ });
57
+ });
40
58
  }
41
59
  //#endregion
42
- export { i as useMermaidRenderer };
60
+ export { o as useMermaidRenderer };
43
61
 
44
62
  //# sourceMappingURL=useMermaidRenderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMermaidRenderer.js","names":[],"sources":["../../../src/components/Markdown/useMermaidRenderer.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport mermaid from 'mermaid';\n\nexport function useMermaidRenderer(\n containerRef: React.RefObject<HTMLDivElement | null>,\n markdown: string,\n enabled: boolean,\n) {\n const [initialized, setInitialized] = useState(false);\n const initRef = useRef(false);\n\n useEffect(() => {\n if (enabled && !initRef.current) {\n mermaid.initialize({\n startOnLoad: false,\n theme: 'default',\n securityLevel: 'loose',\n fontFamily: 'inherit',\n flowchart: {\n useMaxWidth: true,\n htmlLabels: true,\n },\n });\n initRef.current = true;\n setInitialized(true);\n }\n }, [enabled]);\n\n useEffect(() => {\n if (!enabled || !initialized || !markdown || !containerRef.current) return;\n\n const renderMermaid = async () => {\n try {\n const mermaidElements = containerRef.current?.querySelectorAll('.mermaid-diagram');\n if (mermaidElements) {\n for (let i = 0; i < mermaidElements.length; i++) {\n const element = mermaidElements[i] as HTMLElement;\n const code = element.textContent || '';\n if (code.trim()) {\n try {\n const { svg } = await mermaid.render(`mermaid-${Date.now()}-${i}`, code);\n element.innerHTML = svg;\n } catch (err) {\n console.error('Mermaid render error:', err);\n element.innerHTML = `<pre class=\"text-error\">${code}</pre>`;\n }\n }\n }\n }\n } catch (err) {\n console.error('Mermaid rendering failed:', err);\n }\n };\n\n setTimeout(renderMermaid, 100);\n }, [markdown, enabled, initialized, containerRef]);\n}\n"],"mappings":";;;AAGA,SAAgB,EACd,GACA,GACA,GACA;CACA,IAAM,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,IAAU,EAAO,EAAK;CAmB5B,AAjBA,QAAgB;EACd,AAAI,KAAW,CAAC,EAAQ,YACtB,EAAQ,WAAW;GACjB,aAAa;GACb,OAAO;GACP,eAAe;GACf,YAAY;GACZ,WAAW;IACT,aAAa;IACb,YAAY;GACd;EACF,CAAC,GACD,EAAQ,UAAU,IAClB,EAAe,EAAI;CAEvB,GAAG,CAAC,CAAO,CAAC,GAEZ,QAAgB;EACV,AAAC,KAAY,KAAgB,KAAa,EAAa,WAyB3D,WAAW,YAvBuB;GAChC,IAAI;IACF,IAAM,IAAkB,EAAa,SAAS,iBAAiB,kBAAkB;IACjF,IAAI,GACF,KAAK,IAAI,IAAI,GAAG,IAAI,EAAgB,QAAQ,KAAK;KAC/C,IAAM,IAAU,EAAgB,IAC1B,IAAO,EAAQ,eAAe;KACpC,IAAI,EAAK,KAAK,GACZ,IAAI;MACF,IAAM,EAAE,WAAQ,MAAM,EAAQ,OAAO,WAAW,KAAK,IAAI,EAAE,GAAG,KAAK,CAAI;MACvE,EAAQ,YAAY;KACtB,SAAS,GAAK;MAEZ,AADA,QAAQ,MAAM,yBAAyB,CAAG,GAC1C,EAAQ,YAAY,2BAA2B,EAAK;KACtD;IAEJ;GAEJ,SAAS,GAAK;IACZ,QAAQ,MAAM,6BAA6B,CAAG;GAChD;EACF,GAE0B,GAAG;CAC/B,GAAG;EAAC;EAAU;EAAS;EAAa;CAAY,CAAC;AACnD"}
1
+ {"version":3,"file":"useMermaidRenderer.js","names":[],"sources":["../../../src/components/Markdown/useMermaidRenderer.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport mermaid from 'mermaid';\n\ninterface Diagram {\n code: string;\n output: ChildNode | null;\n timer?: ReturnType<typeof setTimeout>;\n}\n\n// Shared across hook instances, including renders started in the same millisecond.\nlet nextRenderId = 0;\n\nfunction readSource(element: HTMLElement, diagram?: Diagram): string {\n const source = element.getAttribute('data-mermaid-content');\n if (source !== null) return source;\n // Retain compatibility with text-only hook consumers without parsing our SVG.\n if (diagram?.output && element.firstChild === diagram.output) return diagram.code;\n return element.querySelector('svg') ? '' : element.textContent || '';\n}\n\nfunction dispose(element: HTMLElement, diagram: Diagram) {\n clearTimeout(diagram.timer);\n if (diagram.output && element.firstChild === diagram.output) {\n // An attribute-only consumer may already have supplied a new (or empty) source.\n element.textContent = readSource(element, diagram);\n }\n}\n\nexport function useMermaidRenderer(\n containerRef: React.RefObject<HTMLDivElement | null>,\n markdown: string,\n enabled: boolean,\n) {\n const initialized = useRef(false);\n const diagrams = useRef(new Map<HTMLElement, Diagram>());\n\n useEffect(() => {\n const entries = diagrams.current;\n return () => {\n entries.forEach((diagram, element) => dispose(element, diagram));\n entries.clear();\n };\n }, []);\n\n // Reconcile every commit: loading/visibility may replace the container even\n // when the markdown string is unchanged. Only changed blocks schedule work.\n useEffect(() => {\n if (enabled && !initialized.current) {\n mermaid.initialize({\n startOnLoad: false,\n theme: 'default',\n securityLevel: 'strict',\n htmlLabels: false,\n fontFamily: 'inherit',\n flowchart: { useMaxWidth: true, htmlLabels: false },\n });\n initialized.current = true;\n }\n\n const entries = diagrams.current;\n const container = containerRef.current;\n const elements = new Set(enabled && markdown && container\n ? container.querySelectorAll<HTMLElement>('.mermaid-diagram') : []);\n\n entries.forEach((diagram, element) => {\n if (!elements.has(element) || readSource(element, diagram) !== diagram.code ||\n (diagram.output && element.firstChild !== diagram.output)) {\n entries.delete(element);\n dispose(element, diagram);\n }\n });\n\n elements.forEach(element => {\n if (entries.has(element)) return;\n const code = readSource(element);\n if (!code.trim()) return;\n const diagram: Diagram = { code, output: null };\n entries.set(element, diagram);\n const canCommit = () => entries.get(element) === diagram &&\n containerRef.current === container && container?.contains(element) &&\n readSource(element, diagram) === code;\n\n diagram.timer = setTimeout(async () => {\n if (!canCommit()) return;\n try {\n const { svg } = await mermaid.render(`dishui-mermaid-${++nextRenderId}`, code);\n if (!canCommit()) return;\n element.innerHTML = svg;\n diagram.output = element.firstChild;\n } catch (err) {\n if (!canCommit()) return;\n console.error('Mermaid render error:', err);\n const fallback = document.createElement('pre');\n fallback.className = 'text-error';\n fallback.textContent = code;\n element.replaceChildren(fallback);\n diagram.output = fallback;\n }\n }, 100);\n });\n });\n}\n"],"mappings":";;;AAUA,IAAI,IAAe;AAEnB,SAAS,EAAW,GAAsB,GAA2B;CACnE,IAAM,IAAS,EAAQ,aAAa,sBAAsB;CAI1D,OAHI,MAAW,OAEX,GAAS,UAAU,EAAQ,eAAe,EAAQ,SAAe,EAAQ,OACtE,EAAQ,cAAc,KAAK,IAAI,KAAK,EAAQ,eAAe,KAHtC;AAI9B;AAEA,SAAS,EAAQ,GAAsB,GAAkB;CAEvD,AADA,aAAa,EAAQ,KAAK,GACtB,EAAQ,UAAU,EAAQ,eAAe,EAAQ,WAEnD,EAAQ,cAAc,EAAW,GAAS,CAAO;AAErD;AAEA,SAAgB,EACd,GACA,GACA,GACA;CACA,IAAM,IAAc,EAAO,EAAK,GAC1B,IAAW,kBAAO,IAAI,IAA0B,CAAC;CAYvD,AAVA,QAAgB;EACd,IAAM,IAAU,EAAS;EACzB,aAAa;GAEX,AADA,EAAQ,SAAS,GAAS,MAAY,EAAQ,GAAS,CAAO,CAAC,GAC/D,EAAQ,MAAM;EAChB;CACF,GAAG,CAAC,CAAC,GAIL,QAAgB;EACd,AAAI,KAAW,CAAC,EAAY,YAC1B,EAAQ,WAAW;GACjB,aAAa;GACb,OAAO;GACP,eAAe;GACf,YAAY;GACZ,YAAY;GACZ,WAAW;IAAE,aAAa;IAAM,YAAY;GAAM;EACpD,CAAC,GACD,EAAY,UAAU;EAGxB,IAAM,IAAU,EAAS,SACnB,IAAY,EAAa,SACzB,IAAW,IAAI,IAAI,KAAW,KAAY,IAC5C,EAAU,iBAA8B,kBAAkB,IAAI,CAAC,CAAC;EAUpE,AARA,EAAQ,SAAS,GAAS,MAAY;GACpC,CAAI,CAAC,EAAS,IAAI,CAAO,KAAK,EAAW,GAAS,CAAO,MAAM,EAAQ,QAClE,EAAQ,UAAU,EAAQ,eAAe,EAAQ,YACpD,EAAQ,OAAO,CAAO,GACtB,EAAQ,GAAS,CAAO;EAE5B,CAAC,GAED,EAAS,SAAQ,MAAW;GAC1B,IAAI,EAAQ,IAAI,CAAO,GAAG;GAC1B,IAAM,IAAO,EAAW,CAAO;GAC/B,IAAI,CAAC,EAAK,KAAK,GAAG;GAClB,IAAM,IAAmB;IAAE;IAAM,QAAQ;GAAK;GAC9C,EAAQ,IAAI,GAAS,CAAO;GAC5B,IAAM,UAAkB,EAAQ,IAAI,CAAO,MAAM,KAC/C,EAAa,YAAY,KAAa,GAAW,SAAS,CAAO,KACjE,EAAW,GAAS,CAAO,MAAM;GAEnC,EAAQ,QAAQ,WAAW,YAAY;IAChC,MAAU,GACf,IAAI;KACF,IAAM,EAAE,WAAQ,MAAM,EAAQ,OAAO,kBAAkB,EAAE,KAAgB,CAAI;KAC7E,IAAI,CAAC,EAAU,GAAG;KAElB,AADA,EAAQ,YAAY,GACpB,EAAQ,SAAS,EAAQ;IAC3B,SAAS,GAAK;KACZ,IAAI,CAAC,EAAU,GAAG;KAClB,QAAQ,MAAM,yBAAyB,CAAG;KAC1C,IAAM,IAAW,SAAS,cAAc,KAAK;KAI7C,AAHA,EAAS,YAAY,cACrB,EAAS,cAAc,GACvB,EAAQ,gBAAgB,CAAQ,GAChC,EAAQ,SAAS;IACnB;GACF,GAAG,GAAG;EACR,CAAC;CACH,CAAC;AACH"}
@@ -9,7 +9,8 @@ export type ProtectedRouteProps = {
9
9
  /**
10
10
  * ProtectedRoute 组件
11
11
  *
12
- * 用于保护需要认证或特定角色才能访问的路由
12
+ * 用于保护需要认证或特定角色才能访问的页面内容。
13
+ * 组件渲染晚于 loader;数据访问必须使用路由 beforeLoad 和服务端授权。
13
14
  *
14
15
  * @example
15
16
  * ```tsx
@@ -1,12 +1,22 @@
1
1
  import { useAuth as e } from "../../contexts/AppContext.js";
2
- import "react";
3
- import { Fragment as t, jsx as n } from "react/jsx-runtime";
2
+ import { useEffect as t, useRef as n } from "react";
3
+ import { Fragment as r, jsx as i } from "react/jsx-runtime";
4
4
  //#region src/components/ProtectedRoute/index.tsx
5
- var r = ({ children: r, requiredRoles: i = [], requireAny: a = !1, fallbackPath: o = "/login", fallback: s }) => {
6
- let { isAuthenticated: c, hasRole: l, hasAnyRole: u } = e();
7
- return c ? i.length > 0 && !(a ? u(i) : i.every((e) => l(e))) ? s ? /* @__PURE__ */ n(t, { children: s }) : (typeof window < "u" && (window.location.href = "/unauthorized"), null) : /* @__PURE__ */ n(t, { children: r }) : s ? /* @__PURE__ */ n(t, { children: s }) : (typeof window < "u" && (window.location.href = o), null);
5
+ var a = ({ children: a, requiredRoles: o = [], requireAny: s = !1, fallbackPath: c = "/login", fallback: l }) => {
6
+ let { isAuthenticated: u, hasRole: d, hasAnyRole: f } = e(), p = o.length === 0 || (s ? f(o) : o.every((e) => d(e))), m = !u || !p, h = u ? "/unauthorized" : c, g = l != null, _ = n(null);
7
+ return t(() => {
8
+ if (!m || g) {
9
+ _.current = null;
10
+ return;
11
+ }
12
+ typeof window < "u" && _.current !== h && (_.current = h, window.location.href = h);
13
+ }, [
14
+ m,
15
+ g,
16
+ h
17
+ ]), m ? g ? /* @__PURE__ */ i(r, { children: l }) : null : /* @__PURE__ */ i(r, { children: a });
8
18
  };
9
19
  //#endregion
10
- export { r as ProtectedRoute, r as default };
20
+ export { a as ProtectedRoute, a as default };
11
21
 
12
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/ProtectedRoute/index.tsx"],"sourcesContent":["import React from 'react';\nimport { useAuth } from '../../contexts/AppContext';\n\nexport type ProtectedRouteProps = {\n children: React.ReactNode;\n requiredRoles?: string[];\n requireAny?: boolean; // 如果为 true,只需要满足任一角色;如果为 false,需要满足所有角色\n fallbackPath?: string;\n fallback?: React.ReactNode;\n};\n\n/**\n * ProtectedRoute 组件\n * \n * 用于保护需要认证或特定角色才能访问的路由\n * \n * @example\n * ```tsx\n * // 基本用法 - 只需要认证\n * <ProtectedRoute>\n * <AdminPanel />\n * </ProtectedRoute>\n * \n * // 需要特定角色\n * <ProtectedRoute requiredRoles={['admin']}>\n * <AdminPanel />\n * </ProtectedRoute>\n * \n * // 需要任一角色\n * <ProtectedRoute requiredRoles={['admin', 'moderator']} requireAny>\n * <ModeratorPanel />\n * </ProtectedRoute>\n * \n * // 自定义回退路径\n * <ProtectedRoute fallbackPath=\"/unauthorized\">\n * <SecretPage />\n * </ProtectedRoute>\n * \n * // 自定义回退组件\n * <ProtectedRoute fallback={<UnauthorizedMessage />}>\n * <SecretPage />\n * </ProtectedRoute>\n * ```\n */\nexport const ProtectedRoute: React.FC<ProtectedRouteProps> = ({\n children,\n requiredRoles = [],\n requireAny = false,\n fallbackPath = '/login',\n fallback,\n}) => {\n const { isAuthenticated, hasRole, hasAnyRole } = useAuth();\n\n // 检查是否已认证\n if (!isAuthenticated) {\n if (fallback) {\n return <>{fallback}</>;\n }\n // 使用 window.location 进行重定向,避免类型问题\n if (typeof window !== 'undefined') {\n window.location.href = fallbackPath;\n }\n return null;\n }\n\n // 如果需要特定角色\n if (requiredRoles.length > 0) {\n const hasRequiredRoles = requireAny \n ? hasAnyRole(requiredRoles)\n : requiredRoles.every(role => hasRole(role));\n\n if (!hasRequiredRoles) {\n if (fallback) {\n return <>{fallback}</>;\n }\n // 使用 window.location 进行重定向\n if (typeof window !== 'undefined') {\n window.location.href = '/unauthorized';\n }\n return null;\n }\n }\n\n return <>{children}</>;\n};\n\nexport default ProtectedRoute;\n\n"],"mappings":";;;;AA4CA,IAAa,KAAiD,EAC5D,aACA,mBAAgB,CAAC,GACjB,gBAAa,IACb,kBAAe,UACf,kBACI;CACJ,IAAM,EAAE,oBAAiB,YAAS,kBAAe,EAAQ;CAgCzD,OA7BK,IAYD,EAAc,SAAS,KAKrB,EAJqB,IACrB,EAAW,CAAa,IACxB,EAAc,OAAM,MAAQ,EAAQ,CAAI,CAAC,KAGvC,IACK,kBAAA,GAAA,EAAA,UAAG,EAAW,CAAA,KAGnB,OAAO,SAAW,QACpB,OAAO,SAAS,OAAO,kBAElB,QAIJ,kBAAA,GAAA,EAAG,YAAW,CAAA,IA5Bf,IACK,kBAAA,GAAA,EAAA,UAAG,EAAW,CAAA,KAGnB,OAAO,SAAW,QACpB,OAAO,SAAS,OAAO,IAElB;AAsBX"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/ProtectedRoute/index.tsx"],"sourcesContent":["import React, { useEffect, useRef } from 'react';\nimport { useAuth } from '../../contexts/AppContext';\n\nexport type ProtectedRouteProps = {\n children: React.ReactNode;\n requiredRoles?: string[];\n requireAny?: boolean; // 如果为 true,只需要满足任一角色;如果为 false,需要满足所有角色\n fallbackPath?: string;\n fallback?: React.ReactNode;\n};\n\n/**\n * ProtectedRoute 组件\n * \n * 用于保护需要认证或特定角色才能访问的页面内容。\n * 组件渲染晚于 loader;数据访问必须使用路由 beforeLoad 和服务端授权。\n * \n * @example\n * ```tsx\n * // 基本用法 - 只需要认证\n * <ProtectedRoute>\n * <AdminPanel />\n * </ProtectedRoute>\n * \n * // 需要特定角色\n * <ProtectedRoute requiredRoles={['admin']}>\n * <AdminPanel />\n * </ProtectedRoute>\n * \n * // 需要任一角色\n * <ProtectedRoute requiredRoles={['admin', 'moderator']} requireAny>\n * <ModeratorPanel />\n * </ProtectedRoute>\n * \n * // 自定义回退路径\n * <ProtectedRoute fallbackPath=\"/unauthorized\">\n * <SecretPage />\n * </ProtectedRoute>\n * \n * // 自定义回退组件\n * <ProtectedRoute fallback={<UnauthorizedMessage />}>\n * <SecretPage />\n * </ProtectedRoute>\n * ```\n */\nexport const ProtectedRoute: React.FC<ProtectedRouteProps> = ({\n children,\n requiredRoles = [],\n requireAny = false,\n fallbackPath = '/login',\n fallback,\n}) => {\n const { isAuthenticated, hasRole, hasAnyRole } = useAuth();\n\n const hasRequiredRoles = requiredRoles.length === 0 || (requireAny\n ? hasAnyRole(requiredRoles)\n : requiredRoles.every(role => hasRole(role)));\n const denied = !isAuthenticated || !hasRequiredRoles;\n const destination = !isAuthenticated ? fallbackPath : '/unauthorized';\n const hasFallback = fallback !== undefined && fallback !== null;\n const lastRedirect = useRef<string | null>(null);\n\n useEffect(() => {\n if (!denied || hasFallback) {\n lastRedirect.current = null;\n return;\n }\n // StrictMode replays effects; a second hard navigation can abort the first.\n if (typeof window !== 'undefined' && lastRedirect.current !== destination) {\n lastRedirect.current = destination;\n window.location.href = destination;\n }\n }, [denied, hasFallback, destination]);\n\n if (denied) return hasFallback ? <>{fallback}</> : null;\n return <>{children}</>;\n};\n\nexport default ProtectedRoute;\n\n"],"mappings":";;;;AA6CA,IAAa,KAAiD,EAC5D,aACA,mBAAgB,CAAC,GACjB,gBAAa,IACb,kBAAe,UACf,kBACI;CACJ,IAAM,EAAE,oBAAiB,YAAS,kBAAe,EAAQ,GAEnD,IAAmB,EAAc,WAAW,MAAM,IACpD,EAAW,CAAa,IACxB,EAAc,OAAM,MAAQ,EAAQ,CAAI,CAAC,IACvC,IAAS,CAAC,KAAmB,CAAC,GAC9B,IAAe,IAAiC,kBAAf,GACjC,IAAc,KAAuC,MACrD,IAAe,EAAsB,IAAI;CAe/C,OAbA,QAAgB;EACd,IAAI,CAAC,KAAU,GAAa;GAC1B,EAAa,UAAU;GACvB;EACF;EAEA,AAAI,OAAO,SAAW,OAAe,EAAa,YAAY,MAC5D,EAAa,UAAU,GACvB,OAAO,SAAS,OAAO;CAE3B,GAAG;EAAC;EAAQ;EAAa;CAAW,CAAC,GAEjC,IAAe,IAAc,kBAAA,GAAA,EAAA,UAAG,EAAW,CAAA,IAAI,OAC5C,kBAAA,GAAA,EAAG,YAAW,CAAA;AACvB"}
@@ -2,10 +2,16 @@ import React from 'react';
2
2
  export interface Option {
3
3
  value: string | number;
4
4
  label: string;
5
+ disabled?: boolean;
5
6
  }
6
7
  export interface SelectProps {
7
8
  /** Accessible name for the combobox. */
8
9
  'aria-label'?: string;
10
+ 'aria-labelledby'?: string;
11
+ 'aria-describedby'?: string;
12
+ /** Optional ID for the combobox; generated per instance when omitted. */
13
+ id?: string;
14
+ disabled?: boolean;
9
15
  options: Option[];
10
16
  value?: string | number | (string | number)[];
11
17
  onChange: (value: string | number | (string | number)[]) => void;