@eternalheart/react-file-preview 1.3.5 → 1.3.7
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.
- package/lib/FilePreviewContent.d.ts +3 -0
- package/lib/FilePreviewContent.d.ts.map +1 -1
- package/lib/FilePreviewEmbed.d.ts +3 -1
- package/lib/FilePreviewEmbed.d.ts.map +1 -1
- package/lib/FilePreviewModal.d.ts +3 -1
- package/lib/FilePreviewModal.d.ts.map +1 -1
- package/lib/ThemeContext.d.ts +18 -0
- package/lib/ThemeContext.d.ts.map +1 -0
- package/lib/chunks/index--zZy1XpK.mjs +299 -0
- package/lib/chunks/index--zZy1XpK.mjs.map +1 -0
- package/lib/chunks/index-B5tBeF0g.mjs +51 -0
- package/lib/chunks/index-B5tBeF0g.mjs.map +1 -0
- package/lib/chunks/index-BEolw_uv.mjs +528 -0
- package/lib/chunks/index-BEolw_uv.mjs.map +1 -0
- package/lib/chunks/index-BIg3vHQf.mjs +110 -0
- package/lib/chunks/index-BIg3vHQf.mjs.map +1 -0
- package/lib/chunks/index-BOEtlHD3.mjs +257 -0
- package/lib/chunks/index-BOEtlHD3.mjs.map +1 -0
- package/lib/chunks/index-BWbuffRN.mjs +152 -0
- package/lib/chunks/index-BWbuffRN.mjs.map +1 -0
- package/lib/chunks/index-BsSx9pGx.mjs +128 -0
- package/lib/chunks/index-BsSx9pGx.mjs.map +1 -0
- package/lib/chunks/index-CQABwGVP.mjs +99 -0
- package/lib/chunks/index-CQABwGVP.mjs.map +1 -0
- package/lib/chunks/index-DOMMMe9f.mjs +96 -0
- package/lib/chunks/index-DOMMMe9f.mjs.map +1 -0
- package/lib/chunks/index-DTH1IvOG.mjs +239 -0
- package/lib/chunks/index-DTH1IvOG.mjs.map +1 -0
- package/lib/chunks/index-D_8IHm6o.mjs +98 -0
- package/lib/chunks/index-D_8IHm6o.mjs.map +1 -0
- package/lib/chunks/index-DinKO2op.mjs +116 -0
- package/lib/chunks/index-DinKO2op.mjs.map +1 -0
- package/lib/chunks/index-LNXbKjrI.mjs +1900 -0
- package/lib/chunks/index-LNXbKjrI.mjs.map +1 -0
- package/lib/chunks/index-Yp36heK8.mjs +193 -0
- package/lib/chunks/index-Yp36heK8.mjs.map +1 -0
- package/lib/chunks/index-qxvk-6P6.mjs +172 -0
- package/lib/chunks/index-qxvk-6P6.mjs.map +1 -0
- package/lib/chunks/index-w0tt7Myw.mjs +96 -0
- package/lib/chunks/index-w0tt7Myw.mjs.map +1 -0
- package/lib/chunks/index-xTq9b3vw.mjs +52 -0
- package/lib/chunks/index-xTq9b3vw.mjs.map +1 -0
- package/lib/chunks/index-zDEwNk3h.mjs +103 -0
- package/lib/chunks/index-zDEwNk3h.mjs.map +1 -0
- package/lib/chunks/useShikiHighlight-DtWg9b8y.mjs +23 -0
- package/lib/chunks/useShikiHighlight-DtWg9b8y.mjs.map +1 -0
- package/lib/chunks/xspreadsheet-CyBXARuf.mjs +5215 -0
- package/lib/chunks/xspreadsheet-CyBXARuf.mjs.map +1 -0
- package/lib/hooks/useShikiHighlight.d.ts +18 -0
- package/lib/hooks/useShikiHighlight.d.ts.map +1 -0
- package/lib/index.cjs +28 -508
- package/lib/index.cjs.map +1 -1
- package/lib/index.css +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.mjs +13 -59140
- package/lib/index.mjs.map +1 -1
- package/lib/renderers/Audio/index.d.ts.map +1 -1
- package/lib/renderers/Json/index.d.ts.map +1 -1
- package/lib/renderers/Markdown/index.d.ts.map +1 -1
- package/lib/renderers/RendererLoading.d.ts +3 -0
- package/lib/renderers/RendererLoading.d.ts.map +1 -0
- package/lib/renderers/Text/index.d.ts.map +1 -1
- package/lib/renderers/Xml/index.d.ts.map +1 -1
- package/lib/renderers/lazy.d.ts +38 -0
- package/lib/renderers/lazy.d.ts.map +1 -0
- package/lib/renderers/toolbar.types.d.ts.map +1 -1
- package/lib/types.d.ts +27 -3
- package/lib/types.d.ts.map +1 -1
- package/package.json +4 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type Locale, type Messages, type Theme } from '@eternalheart/file-preview-core';
|
|
3
3
|
import { PreviewFileInput, CustomRenderer } from './types';
|
|
4
|
+
import type { CustomRendererEventPayload } from '@eternalheart/file-preview-core';
|
|
4
5
|
export interface FilePreviewContentProps {
|
|
5
6
|
files: PreviewFileInput[];
|
|
6
7
|
currentIndex: number;
|
|
@@ -20,6 +21,8 @@ export interface FilePreviewContentProps {
|
|
|
20
21
|
headless?: boolean;
|
|
21
22
|
/** 主题模式,默认 'dark' */
|
|
22
23
|
theme?: Theme;
|
|
24
|
+
/** 自定义渲染器派发的事件出口,载荷为 `{ name, payload, file }` */
|
|
25
|
+
onCustomEvent?: (event: CustomRendererEventPayload) => void;
|
|
23
26
|
}
|
|
24
27
|
export declare const FilePreviewContent: React.FC<FilePreviewContentProps>;
|
|
25
28
|
//# sourceMappingURL=FilePreviewContent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilePreviewContent.d.ts","sourceRoot":"","sources":["../src/FilePreviewContent.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"FilePreviewContent.d.ts","sourceRoot":"","sources":["../src/FilePreviewContent.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsE,MAAM,OAAO,CAAC;AAG3F,OAAO,EAAiC,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAmB,KAAK,KAAK,EAAE,MAAM,iCAAiC,CAAC;AAYzI,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAyB,MAAM,SAAS,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AA8BlF,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtD,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,kDAAkD;IAClD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;CAC7D;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA6hBhE,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PreviewFileInput, CustomRenderer } from './types';
|
|
2
|
-
import type { Locale, Messages, Theme } from '@eternalheart/file-preview-core';
|
|
2
|
+
import type { Locale, Messages, Theme, CustomRendererEventPayload } from '@eternalheart/file-preview-core';
|
|
3
3
|
interface FilePreviewEmbedProps {
|
|
4
4
|
files: PreviewFileInput[];
|
|
5
5
|
currentIndex?: number;
|
|
@@ -19,6 +19,8 @@ interface FilePreviewEmbedProps {
|
|
|
19
19
|
headless?: boolean;
|
|
20
20
|
/** 主题模式,默认 'dark' */
|
|
21
21
|
theme?: Theme;
|
|
22
|
+
/** 自定义渲染器派发的事件出口 */
|
|
23
|
+
onCustomEvent?: (event: CustomRendererEventPayload) => void;
|
|
22
24
|
}
|
|
23
25
|
export declare const FilePreviewEmbed: React.FC<FilePreviewEmbedProps>;
|
|
24
26
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilePreviewEmbed.d.ts","sourceRoot":"","sources":["../src/FilePreviewEmbed.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"FilePreviewEmbed.d.ts","sourceRoot":"","sources":["../src/FilePreviewEmbed.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE3G,UAAU,qBAAqB;IAC7B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,uBAAuB;IACvB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtD,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,oBAAoB;IACpB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;CAC7D;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAqD5D,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PreviewFileInput, CustomRenderer } from './types';
|
|
2
|
-
import type { Locale, Messages, Theme } from '@eternalheart/file-preview-core';
|
|
2
|
+
import type { Locale, Messages, Theme, CustomRendererEventPayload } from '@eternalheart/file-preview-core';
|
|
3
3
|
interface FilePreviewModalProps {
|
|
4
4
|
files: PreviewFileInput[];
|
|
5
5
|
currentIndex: number;
|
|
@@ -15,6 +15,8 @@ interface FilePreviewModalProps {
|
|
|
15
15
|
headless?: boolean;
|
|
16
16
|
/** 主题模式,默认 'dark' */
|
|
17
17
|
theme?: Theme;
|
|
18
|
+
/** 自定义渲染器派发的事件出口 */
|
|
19
|
+
onCustomEvent?: (event: CustomRendererEventPayload) => void;
|
|
18
20
|
}
|
|
19
21
|
export declare const FilePreviewModal: React.FC<FilePreviewModalProps>;
|
|
20
22
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilePreviewModal.d.ts","sourceRoot":"","sources":["../src/FilePreviewModal.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"FilePreviewModal.d.ts","sourceRoot":"","sources":["../src/FilePreviewModal.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE3G,UAAU,qBAAqB;IAC7B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtD,sBAAsB;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,oBAAoB;IACpB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;CAC7D;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAwF5D,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/** 已解析的主题(auto 在 FilePreviewContent 里被解析过) */
|
|
3
|
+
export type ResolvedTheme = 'dark' | 'light';
|
|
4
|
+
export interface ThemeProviderProps {
|
|
5
|
+
theme: ResolvedTheme;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare const ThemeProvider: React.FC<ThemeProviderProps>;
|
|
9
|
+
/**
|
|
10
|
+
* 获取已解析的主题('dark' | 'light')。
|
|
11
|
+
* Provider 之外默认 'dark'(保持组件历史默认行为)。
|
|
12
|
+
*
|
|
13
|
+
* renderer 需要切第三方库的 theme prop(如 react-syntax-highlighter 的 style、
|
|
14
|
+
* shiki 的 theme 选项)时使用;不要用它做 className 分支 —— class 由 CSS 变量
|
|
15
|
+
* 通过根容器 [data-theme] 自动切换。
|
|
16
|
+
*/
|
|
17
|
+
export declare function useResolvedTheme(): ResolvedTheme;
|
|
18
|
+
//# sourceMappingURL=ThemeContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeContext.d.ts","sourceRoot":"","sources":["../src/ThemeContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAEzD,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAI7C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAEtD,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { jsxs as U, jsx as e, Fragment as O } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as H, useState as a, useEffect as L, useCallback as S, useMemo as G, Suspense as J, lazy as Q } from "react";
|
|
3
|
+
import { createPortal as V } from "react-dom";
|
|
4
|
+
import { ChevronRight as K, FolderOpen as ee, Folder as re, FileImage as te, FileText as ne, FileCode as fe, File as se } from "lucide-react";
|
|
5
|
+
import { u as le, c as q, _ as ie, F as pe, v as ae, w as oe, p as ce, d as de } from "./index-LNXbKjrI.mjs";
|
|
6
|
+
const ue = ({
|
|
7
|
+
left: n,
|
|
8
|
+
right: l,
|
|
9
|
+
initialLeftWidth: h = 280,
|
|
10
|
+
minLeftWidth: x = 160,
|
|
11
|
+
maxLeftWidth: d = 640,
|
|
12
|
+
minRightWidth: y = 200,
|
|
13
|
+
storageKey: i,
|
|
14
|
+
desktopMedia: w = "(min-width: 768px)",
|
|
15
|
+
className: g = ""
|
|
16
|
+
}) => {
|
|
17
|
+
const v = H(null), [u, N] = a(() => {
|
|
18
|
+
if (i && typeof window < "u") {
|
|
19
|
+
const s = Number(window.localStorage.getItem(i));
|
|
20
|
+
if (!isNaN(s) && s > 0) return s;
|
|
21
|
+
}
|
|
22
|
+
return h;
|
|
23
|
+
}), [m, b] = a(!1), [r, j] = a(!1);
|
|
24
|
+
L(() => {
|
|
25
|
+
if (typeof window > "u") return;
|
|
26
|
+
const s = window.matchMedia(w), o = () => j(s.matches);
|
|
27
|
+
return o(), s.addEventListener("change", o), () => s.removeEventListener("change", o);
|
|
28
|
+
}, [w]), L(() => {
|
|
29
|
+
if (!m) return;
|
|
30
|
+
const s = (M) => {
|
|
31
|
+
if (!v.current) return;
|
|
32
|
+
const c = v.current.getBoundingClientRect(), R = M.clientX - c.left, T = c.width - y - 6, $ = Math.min(d, T), P = Math.max(x, Math.min($, R));
|
|
33
|
+
N(P);
|
|
34
|
+
}, o = () => b(!1);
|
|
35
|
+
window.addEventListener("mousemove", s), window.addEventListener("mouseup", o);
|
|
36
|
+
const F = document.body.style.cursor, z = document.body.style.userSelect;
|
|
37
|
+
return document.body.style.cursor = "col-resize", document.body.style.userSelect = "none", () => {
|
|
38
|
+
window.removeEventListener("mousemove", s), window.removeEventListener("mouseup", o), document.body.style.cursor = F, document.body.style.userSelect = z;
|
|
39
|
+
};
|
|
40
|
+
}, [m, x, d, y]), L(() => {
|
|
41
|
+
if (!(!i || m))
|
|
42
|
+
try {
|
|
43
|
+
window.localStorage.setItem(i, String(u));
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}, [u, i, m]);
|
|
47
|
+
const D = S((s) => {
|
|
48
|
+
s.preventDefault(), b(!0);
|
|
49
|
+
}, []);
|
|
50
|
+
return /* @__PURE__ */ U(
|
|
51
|
+
"div",
|
|
52
|
+
{
|
|
53
|
+
ref: v,
|
|
54
|
+
className: `rfp-w-full rfp-h-full rfp-flex rfp-flex-col md:rfp-flex-row rfp-min-h-0 rfp-min-w-0 ${g}`,
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ e(
|
|
57
|
+
"div",
|
|
58
|
+
{
|
|
59
|
+
className: "rfp-min-h-0 rfp-min-w-0 rfp-flex-shrink-0 rfp-w-full rfp-max-h-60 md:rfp-h-full md:rfp-max-h-none",
|
|
60
|
+
style: r ? { width: `${u}px` } : void 0,
|
|
61
|
+
children: n
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
/* @__PURE__ */ e(
|
|
65
|
+
"div",
|
|
66
|
+
{
|
|
67
|
+
role: "separator",
|
|
68
|
+
"aria-orientation": "vertical",
|
|
69
|
+
onMouseDown: D,
|
|
70
|
+
className: `rfp-hidden md:rfp-block rfp-relative rfp-w-1.5 rfp-flex-shrink-0 rfp-cursor-col-resize rfp-transition-colors ${m ? "rfp-bg-surface-toolbar" : "rfp-bg-surface-2 hover:rfp-bg-surface-3"}`,
|
|
71
|
+
children: /* @__PURE__ */ e("span", { className: "rfp-absolute rfp-inset-y-0 -rfp-left-1 -rfp-right-1" })
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ e("div", { className: "rfp-flex-1 rfp-min-w-0 rfp-min-h-0 rfp-overflow-hidden", children: l })
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}, me = Q(
|
|
79
|
+
() => import("./index-LNXbKjrI.mjs").then((n) => n.q).then((n) => ({ default: n.FilePreviewContent }))
|
|
80
|
+
), he = (n) => {
|
|
81
|
+
const l = de({ name: n, type: "" });
|
|
82
|
+
return l === "image" ? te : l === "text" || l === "markdown" || l === "json" || l === "csv" || l === "xml" || l === "subtitle" ? n.endsWith(".md") || n.endsWith(".markdown") ? ne : fe : se;
|
|
83
|
+
}, Z = ({
|
|
84
|
+
node: n,
|
|
85
|
+
depth: l,
|
|
86
|
+
selectedPath: h,
|
|
87
|
+
expanded: x,
|
|
88
|
+
onToggle: d,
|
|
89
|
+
onSelect: y,
|
|
90
|
+
onHover: i,
|
|
91
|
+
onLeave: w
|
|
92
|
+
}) => {
|
|
93
|
+
var b;
|
|
94
|
+
const g = x.has(n.path), v = h === n.path, u = { paddingLeft: `${l * 14 + 10}px` }, N = (r) => {
|
|
95
|
+
const j = r.currentTarget.getBoundingClientRect();
|
|
96
|
+
i(n.name || "/", j);
|
|
97
|
+
};
|
|
98
|
+
if (n.isDir)
|
|
99
|
+
return /* @__PURE__ */ U(O, { children: [
|
|
100
|
+
/* @__PURE__ */ U(
|
|
101
|
+
"button",
|
|
102
|
+
{
|
|
103
|
+
type: "button",
|
|
104
|
+
onClick: () => d(n.path),
|
|
105
|
+
onMouseEnter: N,
|
|
106
|
+
onMouseLeave: w,
|
|
107
|
+
className: "rfp-w-full rfp-flex rfp-items-center rfp-gap-1.5 rfp-py-1.5 rfp-pr-2 rfp-text-left rfp-text-fg-secondary hover:rfp-bg-surface-1 rfp-text-sm",
|
|
108
|
+
style: u,
|
|
109
|
+
children: [
|
|
110
|
+
/* @__PURE__ */ e(
|
|
111
|
+
K,
|
|
112
|
+
{
|
|
113
|
+
className: `rfp-w-3.5 rfp-h-3.5 rfp-flex-shrink-0 rfp-transition-transform ${g ? "rfp-rotate-90" : ""}`
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
g ? /* @__PURE__ */ e(ee, { className: "rfp-w-4 rfp-h-4 rfp-flex-shrink-0 rfp-text-amber-300/80" }) : /* @__PURE__ */ e(re, { className: "rfp-w-4 rfp-h-4 rfp-flex-shrink-0 rfp-text-amber-300/80" }),
|
|
117
|
+
/* @__PURE__ */ e("span", { className: "rfp-truncate rfp-flex-1 rfp-min-w-0", children: n.name || "/" })
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
),
|
|
121
|
+
g && ((b = n.children) == null ? void 0 : b.map((r) => /* @__PURE__ */ e(
|
|
122
|
+
Z,
|
|
123
|
+
{
|
|
124
|
+
node: r,
|
|
125
|
+
depth: l + 1,
|
|
126
|
+
selectedPath: h,
|
|
127
|
+
expanded: x,
|
|
128
|
+
onToggle: d,
|
|
129
|
+
onSelect: y,
|
|
130
|
+
onHover: i,
|
|
131
|
+
onLeave: w
|
|
132
|
+
},
|
|
133
|
+
r.path
|
|
134
|
+
)))
|
|
135
|
+
] });
|
|
136
|
+
const m = he(n.name);
|
|
137
|
+
return /* @__PURE__ */ U(
|
|
138
|
+
"button",
|
|
139
|
+
{
|
|
140
|
+
type: "button",
|
|
141
|
+
onClick: () => y(n),
|
|
142
|
+
onMouseEnter: N,
|
|
143
|
+
onMouseLeave: w,
|
|
144
|
+
className: `rfp-w-full rfp-flex rfp-items-center rfp-gap-1.5 rfp-py-1.5 rfp-pr-2 rfp-text-left rfp-text-sm ${v ? "rfp-bg-surface-2 rfp-text-fg-primary" : "rfp-text-fg-secondary hover:rfp-bg-surface-1"}`,
|
|
145
|
+
style: u,
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ e("span", { className: "rfp-w-3.5 rfp-h-3.5 rfp-flex-shrink-0" }),
|
|
148
|
+
/* @__PURE__ */ e(m, { className: "rfp-w-4 rfp-h-4 rfp-flex-shrink-0 rfp-text-fg-tertiary" }),
|
|
149
|
+
/* @__PURE__ */ e("span", { className: "rfp-flex-1 rfp-truncate rfp-min-w-0", children: n.name }),
|
|
150
|
+
/* @__PURE__ */ e("span", { className: "rfp-text-xs rfp-text-fg-disabled rfp-flex-shrink-0 rfp-ml-2", children: ce(n.size) })
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}, ge = ({ url: n, nestingDepth: l = 0, onStatsChange: h }) => {
|
|
155
|
+
var W;
|
|
156
|
+
const x = le(), [d, y] = a(null), [i, w] = a(null), [g, v] = a(!0), [u, N] = a(null), [m, b] = a(/* @__PURE__ */ new Set([""])), [r, j] = a(null), [D, s] = a(!1), [o, F] = a(null), [z, M] = a(null), c = H(h);
|
|
157
|
+
L(() => {
|
|
158
|
+
c.current = h;
|
|
159
|
+
}, [h]), L(() => {
|
|
160
|
+
let t = !1;
|
|
161
|
+
return (async () => {
|
|
162
|
+
try {
|
|
163
|
+
v(!0), N(null);
|
|
164
|
+
const p = await fetch(n);
|
|
165
|
+
if (!p.ok) throw new Error("加载失败");
|
|
166
|
+
const E = await p.arrayBuffer(), k = await pe(E);
|
|
167
|
+
if (t) return;
|
|
168
|
+
const I = ae(k), C = oe(I);
|
|
169
|
+
y(k), w(C);
|
|
170
|
+
const _ = /* @__PURE__ */ new Set([""]);
|
|
171
|
+
if (C.children)
|
|
172
|
+
for (const B of C.children) B.isDir && _.add(B.path);
|
|
173
|
+
b(_);
|
|
174
|
+
} catch (p) {
|
|
175
|
+
console.error(p), t || N(x("zip.load_failed"));
|
|
176
|
+
} finally {
|
|
177
|
+
t || v(!1);
|
|
178
|
+
}
|
|
179
|
+
})(), () => {
|
|
180
|
+
t = !0;
|
|
181
|
+
};
|
|
182
|
+
}, [n]), L(() => () => {
|
|
183
|
+
r != null && r.blobUrl && URL.revokeObjectURL(r.blobUrl);
|
|
184
|
+
}, [r]);
|
|
185
|
+
const R = G(() => {
|
|
186
|
+
if (!i) return null;
|
|
187
|
+
let t = 0, f = 0, p = 0;
|
|
188
|
+
const E = (k) => {
|
|
189
|
+
var I;
|
|
190
|
+
k.isDir ? (k.path && f++, (I = k.children) == null || I.forEach(E)) : (t++, p += k.size);
|
|
191
|
+
};
|
|
192
|
+
return E(i), { files: t, dirs: f, size: p };
|
|
193
|
+
}, [i]);
|
|
194
|
+
L(() => {
|
|
195
|
+
var t;
|
|
196
|
+
return (t = c.current) == null || t.call(c, R), () => {
|
|
197
|
+
var f;
|
|
198
|
+
(f = c.current) == null || f.call(c, null);
|
|
199
|
+
};
|
|
200
|
+
}, [R]);
|
|
201
|
+
const T = S((t) => {
|
|
202
|
+
b((f) => {
|
|
203
|
+
const p = new Set(f);
|
|
204
|
+
return p.has(t) ? p.delete(t) : p.add(t), p;
|
|
205
|
+
});
|
|
206
|
+
}, []), $ = S((t, f) => {
|
|
207
|
+
M({
|
|
208
|
+
text: t,
|
|
209
|
+
x: f.right + 8,
|
|
210
|
+
y: f.top + f.height / 2
|
|
211
|
+
});
|
|
212
|
+
}, []), P = S(() => {
|
|
213
|
+
M(null);
|
|
214
|
+
}, []), X = S(
|
|
215
|
+
async (t) => {
|
|
216
|
+
if (!(!d || t.isDir)) {
|
|
217
|
+
r != null && r.blobUrl && URL.revokeObjectURL(r.blobUrl), s(!0), F(null);
|
|
218
|
+
try {
|
|
219
|
+
const f = q(t.name), p = await ie(d, t.path, f !== "application/octet-stream" ? f : void 0), E = URL.createObjectURL(p);
|
|
220
|
+
j({ path: t.path, name: t.name, size: t.size, blobUrl: E });
|
|
221
|
+
} catch (f) {
|
|
222
|
+
console.error(f), F("条目读取失败");
|
|
223
|
+
} finally {
|
|
224
|
+
s(!1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
[d, r]
|
|
229
|
+
);
|
|
230
|
+
if (g)
|
|
231
|
+
return /* @__PURE__ */ e("div", { className: "rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full", children: /* @__PURE__ */ e("div", { className: "rfp-w-12 rfp-h-12 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin" }) });
|
|
232
|
+
if (u || !i)
|
|
233
|
+
return /* @__PURE__ */ e("div", { className: "rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full", children: /* @__PURE__ */ e("div", { className: "rfp-text-fg-secondary rfp-text-center", children: /* @__PURE__ */ e("p", { className: "rfp-text-lg", children: u || x("zip.parse_failed") }) }) });
|
|
234
|
+
const Y = /* @__PURE__ */ e("div", { className: "rfp-w-full rfp-h-full rfp-overflow-auto", children: (W = i.children) == null ? void 0 : W.map((t) => /* @__PURE__ */ e(
|
|
235
|
+
Z,
|
|
236
|
+
{
|
|
237
|
+
node: t,
|
|
238
|
+
depth: 0,
|
|
239
|
+
selectedPath: (r == null ? void 0 : r.path) ?? null,
|
|
240
|
+
expanded: m,
|
|
241
|
+
onToggle: T,
|
|
242
|
+
onSelect: X,
|
|
243
|
+
onHover: $,
|
|
244
|
+
onLeave: P
|
|
245
|
+
},
|
|
246
|
+
t.path
|
|
247
|
+
)) }), A = /* @__PURE__ */ U("div", { className: "rfp-w-full rfp-h-full rfp-flex rfp-flex-col", children: [
|
|
248
|
+
!r && /* @__PURE__ */ e("div", { className: "rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center rfp-text-fg-muted rfp-text-sm rfp-p-6", children: "从左侧选择一个文件以预览" }),
|
|
249
|
+
r && D && /* @__PURE__ */ e("div", { className: "rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center", children: /* @__PURE__ */ e("div", { className: "rfp-w-8 rfp-h-8 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin" }) }),
|
|
250
|
+
r && !D && o && /* @__PURE__ */ e("div", { className: "rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center rfp-text-fg-secondary", children: o }),
|
|
251
|
+
r && !D && !o && /* @__PURE__ */ e(O, { children: /* @__PURE__ */ e("div", { className: "rfp-flex-1 rfp-min-h-0 rfp-overflow-hidden rfp-flex rfp-relative rfp-z-0", children: /* @__PURE__ */ e(
|
|
252
|
+
J,
|
|
253
|
+
{
|
|
254
|
+
fallback: /* @__PURE__ */ e("div", { className: "rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center", children: /* @__PURE__ */ e("div", { className: "rfp-w-8 rfp-h-8 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin" }) }),
|
|
255
|
+
children: /* @__PURE__ */ e(
|
|
256
|
+
me,
|
|
257
|
+
{
|
|
258
|
+
mode: "embed",
|
|
259
|
+
files: [{ name: r.name, url: r.blobUrl, type: q(r.name) }],
|
|
260
|
+
currentIndex: 0,
|
|
261
|
+
zipNestingDepth: l + 1
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
) }) })
|
|
266
|
+
] });
|
|
267
|
+
return /* @__PURE__ */ U(O, { children: [
|
|
268
|
+
/* @__PURE__ */ e(
|
|
269
|
+
ue,
|
|
270
|
+
{
|
|
271
|
+
left: Y,
|
|
272
|
+
right: A,
|
|
273
|
+
initialLeftWidth: 280,
|
|
274
|
+
minLeftWidth: 180,
|
|
275
|
+
maxLeftWidth: 560,
|
|
276
|
+
storageKey: "rfp-zip-split-left"
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
z && typeof document < "u" && V(
|
|
280
|
+
/* @__PURE__ */ e(
|
|
281
|
+
"div",
|
|
282
|
+
{
|
|
283
|
+
className: "rfp-fixed rfp-z-[9999] rfp-pointer-events-none rfp-px-2 rfp-py-1 rfp-bg-[rgba(0,0,0,0.85)] rfp-text-fg-primary rfp-text-xs rfp-rounded rfp-whitespace-nowrap rfp-shadow-lg",
|
|
284
|
+
style: {
|
|
285
|
+
left: `${z.x}px`,
|
|
286
|
+
top: `${z.y}px`,
|
|
287
|
+
transform: "translateY(-50%)"
|
|
288
|
+
},
|
|
289
|
+
children: z.text
|
|
290
|
+
}
|
|
291
|
+
),
|
|
292
|
+
document.body
|
|
293
|
+
)
|
|
294
|
+
] });
|
|
295
|
+
};
|
|
296
|
+
export {
|
|
297
|
+
ge as ZipRenderer
|
|
298
|
+
};
|
|
299
|
+
//# sourceMappingURL=index--zZy1XpK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index--zZy1XpK.mjs","sources":["../../src/components/ResizableSplit.tsx","../../src/renderers/Zip/index.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\n\nexport interface ResizableSplitProps {\n /** 左侧内容 */\n left: React.ReactNode;\n /** 右侧内容 */\n right: React.ReactNode;\n /** 左侧初始宽度(px);传入 storageKey 时会从 localStorage 读取 */\n initialLeftWidth?: number;\n /** 左侧最小宽度(px) */\n minLeftWidth?: number;\n /** 左侧最大宽度(px),同时不超过 `容器宽 - minRightWidth - 分隔线宽` */\n maxLeftWidth?: number;\n /** 右侧至少保留的宽度(px) */\n minRightWidth?: number;\n /** localStorage 持久化 key;不传则不持久化 */\n storageKey?: string;\n /** 启用横向拖动的媒体查询,默认 `(min-width: 768px)` */\n desktopMedia?: string;\n /** 容器额外类名 */\n className?: string;\n}\n\n/**\n * 通用可拖动分隔布局:\n * - 桌面端(由 `desktopMedia` 判定)横向分两栏,中间分隔线可左右拖动调整左栏宽度\n * - 移动端退化为上下堆叠,不显示分隔线\n * - 可选 `storageKey` 将宽度持久化到 localStorage\n */\nexport const ResizableSplit: React.FC<ResizableSplitProps> = ({\n left,\n right,\n initialLeftWidth = 280,\n minLeftWidth = 160,\n maxLeftWidth = 640,\n minRightWidth = 200,\n storageKey,\n desktopMedia = '(min-width: 768px)',\n className = '',\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const [leftWidth, setLeftWidth] = useState<number>(() => {\n if (storageKey && typeof window !== 'undefined') {\n const saved = Number(window.localStorage.getItem(storageKey));\n if (!isNaN(saved) && saved > 0) return saved;\n }\n return initialLeftWidth;\n });\n const [dragging, setDragging] = useState(false);\n const [isDesktop, setIsDesktop] = useState(false);\n\n // 响应式:监听媒体查询\n useEffect(() => {\n if (typeof window === 'undefined') return;\n const mq = window.matchMedia(desktopMedia);\n const handler = () => setIsDesktop(mq.matches);\n handler();\n mq.addEventListener('change', handler);\n return () => mq.removeEventListener('change', handler);\n }, [desktopMedia]);\n\n // 拖动\n useEffect(() => {\n if (!dragging) return;\n const handleMove = (e: MouseEvent) => {\n if (!containerRef.current) return;\n const rect = containerRef.current.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const cap = rect.width - minRightWidth - 6;\n const effectiveMax = Math.min(maxLeftWidth, cap);\n const newW = Math.max(minLeftWidth, Math.min(effectiveMax, x));\n setLeftWidth(newW);\n };\n const handleUp = () => setDragging(false);\n window.addEventListener('mousemove', handleMove);\n window.addEventListener('mouseup', handleUp);\n const prevCursor = document.body.style.cursor;\n const prevSelect = document.body.style.userSelect;\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n return () => {\n window.removeEventListener('mousemove', handleMove);\n window.removeEventListener('mouseup', handleUp);\n document.body.style.cursor = prevCursor;\n document.body.style.userSelect = prevSelect;\n };\n }, [dragging, minLeftWidth, maxLeftWidth, minRightWidth]);\n\n // 持久化\n useEffect(() => {\n if (!storageKey || dragging) return;\n try {\n window.localStorage.setItem(storageKey, String(leftWidth));\n } catch {\n // ignore\n }\n }, [leftWidth, storageKey, dragging]);\n\n const handleDividerDown = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n setDragging(true);\n }, []);\n\n return (\n <div\n ref={containerRef}\n className={`rfp-w-full rfp-h-full rfp-flex rfp-flex-col md:rfp-flex-row rfp-min-h-0 rfp-min-w-0 ${className}`}\n >\n <div\n className=\"rfp-min-h-0 rfp-min-w-0 rfp-flex-shrink-0 rfp-w-full rfp-max-h-60 md:rfp-h-full md:rfp-max-h-none\"\n style={isDesktop ? { width: `${leftWidth}px` } : undefined}\n >\n {left}\n </div>\n {/* 分隔线:仅桌面显示 */}\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n onMouseDown={handleDividerDown}\n className={`rfp-hidden md:rfp-block rfp-relative rfp-w-1.5 rfp-flex-shrink-0 rfp-cursor-col-resize rfp-transition-colors ${\n dragging ? 'rfp-bg-surface-toolbar' : 'rfp-bg-surface-2 hover:rfp-bg-surface-3'\n }`}\n >\n {/* 加宽命中区,改善拖动体验 */}\n <span className=\"rfp-absolute rfp-inset-y-0 -rfp-left-1 -rfp-right-1\" />\n </div>\n <div className=\"rfp-flex-1 rfp-min-w-0 rfp-min-h-0 rfp-overflow-hidden\">{right}</div>\n </div>\n );\n};\n","import { useState, useEffect, useMemo, useCallback, useRef, lazy, Suspense } from 'react';\nimport React from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n Folder,\n FolderOpen,\n FileText,\n FileImage,\n FileCode,\n File as FileIcon,\n ChevronRight,\n} from 'lucide-react';\nimport type JSZip from 'jszip';\nimport {\n loadZip,\n listZipEntries,\n buildZipTree,\n readZipEntryBlob,\n formatFileSize,\n getFileType,\n inferMimeType,\n type ZipTreeNode,\n} from '@eternalheart/file-preview-core';\nimport { ResizableSplit } from '../../components/ResizableSplit';\nimport type { ZipToolbarStats } from './toolbar';\nimport { useTranslator } from '../../i18n/LocaleContext';\n\n// 懒加载 FilePreviewContent 以打破循环依赖\nconst LazyFilePreviewContent = lazy(() =>\n import('../../FilePreviewContent').then(m => ({ default: m.FilePreviewContent }))\n);\n\ninterface ZipRendererProps {\n url: string;\n /** ZIP 嵌套深度(由 FilePreviewContent 传入) */\n nestingDepth?: number;\n /** 解析完成后向外回报统计信息(files / dirs / size),供工具栏展示 */\n onStatsChange?: (stats: ZipToolbarStats | null) => void;\n}\n\ninterface SelectedPreview {\n path: string;\n name: string;\n size: number;\n blobUrl: string;\n}\n\n/** 根据文件类型返回树节点图标 */\nconst resolveIcon = (name: string) => {\n const ft = getFileType({ id: '', name, url: '', type: '' });\n if (ft === 'image') return FileImage;\n if (ft === 'text' || ft === 'markdown' || ft === 'json' || ft === 'csv' || ft === 'xml' || ft === 'subtitle') {\n return name.endsWith('.md') || name.endsWith('.markdown') ? FileText : FileCode;\n }\n return FileIcon;\n};\n\n// ---------- Tooltip via portal ----------\n\ninterface HoverTipState {\n text: string;\n x: number;\n y: number;\n}\n\n// ---------- Tree item ----------\n\ninterface TreeItemProps {\n node: ZipTreeNode;\n depth: number;\n selectedPath: string | null;\n expanded: Set<string>;\n onToggle: (path: string) => void;\n onSelect: (node: ZipTreeNode) => void;\n onHover: (text: string, rect: DOMRect) => void;\n onLeave: () => void;\n}\n\nconst TreeItem: React.FC<TreeItemProps> = ({\n node,\n depth,\n selectedPath,\n expanded,\n onToggle,\n onSelect,\n onHover,\n onLeave,\n}) => {\n const isOpen = expanded.has(node.path);\n const isSelected = selectedPath === node.path;\n const pad = { paddingLeft: `${depth * 14 + 10}px` };\n const handleEnter = (e: React.MouseEvent<HTMLElement>) => {\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n onHover(node.name || '/', rect);\n };\n\n if (node.isDir) {\n return (\n <>\n <button\n type=\"button\"\n onClick={() => onToggle(node.path)}\n onMouseEnter={handleEnter}\n onMouseLeave={onLeave}\n className=\"rfp-w-full rfp-flex rfp-items-center rfp-gap-1.5 rfp-py-1.5 rfp-pr-2 rfp-text-left rfp-text-fg-secondary hover:rfp-bg-surface-1 rfp-text-sm\"\n style={pad}\n >\n <ChevronRight\n className={`rfp-w-3.5 rfp-h-3.5 rfp-flex-shrink-0 rfp-transition-transform ${\n isOpen ? 'rfp-rotate-90' : ''\n }`}\n />\n {isOpen ? (\n <FolderOpen className=\"rfp-w-4 rfp-h-4 rfp-flex-shrink-0 rfp-text-amber-300/80\" />\n ) : (\n <Folder className=\"rfp-w-4 rfp-h-4 rfp-flex-shrink-0 rfp-text-amber-300/80\" />\n )}\n <span className=\"rfp-truncate rfp-flex-1 rfp-min-w-0\">{node.name || '/'}</span>\n </button>\n {isOpen &&\n node.children?.map((child) => (\n <TreeItem\n key={child.path}\n node={child}\n depth={depth + 1}\n selectedPath={selectedPath}\n expanded={expanded}\n onToggle={onToggle}\n onSelect={onSelect}\n onHover={onHover}\n onLeave={onLeave}\n />\n ))}\n </>\n );\n }\n\n const Icon = resolveIcon(node.name);\n\n return (\n <button\n type=\"button\"\n onClick={() => onSelect(node)}\n onMouseEnter={handleEnter}\n onMouseLeave={onLeave}\n className={`rfp-w-full rfp-flex rfp-items-center rfp-gap-1.5 rfp-py-1.5 rfp-pr-2 rfp-text-left rfp-text-sm ${\n isSelected ? 'rfp-bg-surface-2 rfp-text-fg-primary' : 'rfp-text-fg-secondary hover:rfp-bg-surface-1'\n }`}\n style={pad}\n >\n <span className=\"rfp-w-3.5 rfp-h-3.5 rfp-flex-shrink-0\" />\n <Icon className=\"rfp-w-4 rfp-h-4 rfp-flex-shrink-0 rfp-text-fg-tertiary\" />\n <span className=\"rfp-flex-1 rfp-truncate rfp-min-w-0\">{node.name}</span>\n <span className=\"rfp-text-xs rfp-text-fg-disabled rfp-flex-shrink-0 rfp-ml-2\">\n {formatFileSize(node.size)}\n </span>\n </button>\n );\n};\n\n// ---------- Main Zip Renderer ----------\n\nexport const ZipRenderer: React.FC<ZipRendererProps> = ({ url, nestingDepth = 0, onStatsChange }) => {\n const t = useTranslator();\n const [zip, setZip] = useState<JSZip | null>(null);\n const [tree, setTree] = useState<ZipTreeNode | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [expanded, setExpanded] = useState<Set<string>>(new Set(['']));\n const [selected, setSelected] = useState<SelectedPreview | null>(null);\n const [previewLoading, setPreviewLoading] = useState(false);\n const [previewError, setPreviewError] = useState<string | null>(null);\n const [hoverTip, setHoverTip] = useState<HoverTipState | null>(null);\n const onStatsChangeRef = useRef(onStatsChange);\n\n useEffect(() => {\n onStatsChangeRef.current = onStatsChange;\n }, [onStatsChange]);\n\n useEffect(() => {\n let cancelled = false;\n const load = async () => {\n try {\n setLoading(true);\n setError(null);\n const res = await fetch(url);\n if (!res.ok) throw new Error('加载失败');\n const buf = await res.arrayBuffer();\n const z = await loadZip(buf);\n if (cancelled) return;\n const entries = listZipEntries(z);\n const root = buildZipTree(entries);\n setZip(z);\n setTree(root);\n const init = new Set<string>(['']);\n if (root.children) {\n for (const c of root.children) if (c.isDir) init.add(c.path);\n }\n setExpanded(init);\n } catch (err) {\n console.error(err);\n if (!cancelled) setError(t('zip.load_failed'));\n } finally {\n if (!cancelled) setLoading(false);\n }\n };\n load();\n return () => {\n cancelled = true;\n };\n }, [url]);\n\n // 切换文件时回收 blob URL\n useEffect(() => {\n return () => {\n if (selected?.blobUrl) URL.revokeObjectURL(selected.blobUrl);\n };\n }, [selected]);\n\n const totalStats = useMemo<ZipToolbarStats | null>(() => {\n if (!tree) return null;\n let files = 0;\n let dirs = 0;\n let size = 0;\n const walk = (n: ZipTreeNode) => {\n if (n.isDir) {\n if (n.path) dirs++;\n n.children?.forEach(walk);\n } else {\n files++;\n size += n.size;\n }\n };\n walk(tree);\n return { files, dirs, size };\n }, [tree]);\n\n // 向外回报 stats\n useEffect(() => {\n onStatsChangeRef.current?.(totalStats);\n return () => {\n onStatsChangeRef.current?.(null);\n };\n }, [totalStats]);\n\n const handleToggle = useCallback((path: string) => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(path)) next.delete(path);\n else next.add(path);\n return next;\n });\n }, []);\n\n const handleHover = useCallback((text: string, rect: DOMRect) => {\n setHoverTip({\n text,\n x: rect.right + 8,\n y: rect.top + rect.height / 2,\n });\n }, []);\n\n const handleLeave = useCallback(() => {\n setHoverTip(null);\n }, []);\n\n const handleSelect = useCallback(\n async (node: ZipTreeNode) => {\n if (!zip || node.isDir) return;\n if (selected?.blobUrl) URL.revokeObjectURL(selected.blobUrl);\n setPreviewLoading(true);\n setPreviewError(null);\n\n try {\n const mime = inferMimeType(node.name);\n const blob = await readZipEntryBlob(zip, node.path, mime !== 'application/octet-stream' ? mime : undefined);\n const blobUrl = URL.createObjectURL(blob);\n setSelected({ path: node.path, name: node.name, size: node.size, blobUrl });\n } catch (err) {\n console.error(err);\n setPreviewError('条目读取失败');\n } finally {\n setPreviewLoading(false);\n }\n },\n [zip, selected]\n );\n\n if (loading) {\n return (\n <div className=\"rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full\">\n <div className=\"rfp-w-12 rfp-h-12 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin\" />\n </div>\n );\n }\n\n if (error || !tree) {\n return (\n <div className=\"rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full\">\n <div className=\"rfp-text-fg-secondary rfp-text-center\">\n <p className=\"rfp-text-lg\">{error || t('zip.parse_failed')}</p>\n </div>\n </div>\n );\n }\n\n // 左侧:文件树\n const leftPane = (\n <div className=\"rfp-w-full rfp-h-full rfp-overflow-auto\">\n {tree.children?.map((child) => (\n <TreeItem\n key={child.path}\n node={child}\n depth={0}\n selectedPath={selected?.path ?? null}\n expanded={expanded}\n onToggle={handleToggle}\n onSelect={handleSelect}\n onHover={handleHover}\n onLeave={handleLeave}\n />\n ))}\n </div>\n );\n\n // 右侧:预览区\n const rightPane = (\n <div className=\"rfp-w-full rfp-h-full rfp-flex rfp-flex-col\">\n {!selected && (\n <div className=\"rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center rfp-text-fg-muted rfp-text-sm rfp-p-6\">\n 从左侧选择一个文件以预览\n </div>\n )}\n {selected && previewLoading && (\n <div className=\"rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center\">\n <div className=\"rfp-w-8 rfp-h-8 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin\" />\n </div>\n )}\n {selected && !previewLoading && previewError && (\n <div className=\"rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center rfp-text-fg-secondary\">\n {previewError}\n </div>\n )}\n {selected && !previewLoading && !previewError && (\n <>\n <div className=\"rfp-flex-1 rfp-min-h-0 rfp-overflow-hidden rfp-flex rfp-relative rfp-z-0\">\n <Suspense\n fallback={\n <div className=\"rfp-flex-1 rfp-flex rfp-items-center rfp-justify-center\">\n <div className=\"rfp-w-8 rfp-h-8 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin\" />\n </div>\n }\n >\n <LazyFilePreviewContent\n mode=\"embed\"\n files={[{ name: selected.name, url: selected.blobUrl, type: inferMimeType(selected.name) }]}\n currentIndex={0}\n zipNestingDepth={nestingDepth + 1}\n />\n </Suspense>\n </div>\n </>\n )}\n </div>\n );\n\n return (\n <>\n <ResizableSplit\n left={leftPane}\n right={rightPane}\n initialLeftWidth={280}\n minLeftWidth={180}\n maxLeftWidth={560}\n storageKey=\"rfp-zip-split-left\"\n />\n {/* 文件名 hover tooltip(portal 到 body,避免被滚动区裁剪) */}\n {hoverTip &&\n typeof document !== 'undefined' &&\n createPortal(\n <div\n className=\"rfp-fixed rfp-z-[9999] rfp-pointer-events-none rfp-px-2 rfp-py-1 rfp-bg-[rgba(0,0,0,0.85)] rfp-text-fg-primary rfp-text-xs rfp-rounded rfp-whitespace-nowrap rfp-shadow-lg\"\n style={{\n left: `${hoverTip.x}px`,\n top: `${hoverTip.y}px`,\n transform: 'translateY(-50%)',\n }}\n >\n {hoverTip.text}\n </div>,\n document.body\n )}\n </>\n );\n};\n"],"names":["ResizableSplit","left","right","initialLeftWidth","minLeftWidth","maxLeftWidth","minRightWidth","storageKey","desktopMedia","className","containerRef","useRef","leftWidth","setLeftWidth","useState","saved","dragging","setDragging","isDesktop","setIsDesktop","useEffect","mq","handler","handleMove","e","rect","x","cap","effectiveMax","newW","handleUp","prevCursor","prevSelect","handleDividerDown","useCallback","jsxs","jsx","LazyFilePreviewContent","lazy","m","resolveIcon","name","ft","getFileType","FileImage","FileText","FileCode","FileIcon","TreeItem","node","depth","selectedPath","expanded","onToggle","onSelect","onHover","onLeave","isOpen","isSelected","pad","handleEnter","Fragment","ChevronRight","FolderOpen","Folder","_a","child","Icon","formatFileSize","ZipRenderer","url","nestingDepth","onStatsChange","t","useTranslator","zip","setZip","tree","setTree","loading","setLoading","error","setError","setExpanded","selected","setSelected","previewLoading","setPreviewLoading","previewError","setPreviewError","hoverTip","setHoverTip","onStatsChangeRef","cancelled","res","buf","z","loadZip","entries","listZipEntries","root","buildZipTree","init","c","err","totalStats","useMemo","files","dirs","size","walk","n","handleToggle","path","prev","next","handleHover","text","handleLeave","handleSelect","mime","inferMimeType","blob","readZipEntryBlob","blobUrl","leftPane","rightPane","Suspense","createPortal"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as t } from "react/jsx-runtime";
|
|
2
|
+
import { useState as p, useEffect as d } from "react";
|
|
3
|
+
import { u as m, h as u } from "./index-LNXbKjrI.mjs";
|
|
4
|
+
import { u as h } from "./useShikiHighlight-DtWg9b8y.mjs";
|
|
5
|
+
const g = (e) => {
|
|
6
|
+
try {
|
|
7
|
+
const s = new DOMParser().parseFromString(e, "application/xml");
|
|
8
|
+
if (s.querySelector("parsererror")) return e;
|
|
9
|
+
const r = new XMLSerializer().serializeToString(s);
|
|
10
|
+
return w(r);
|
|
11
|
+
} catch {
|
|
12
|
+
return e;
|
|
13
|
+
}
|
|
14
|
+
}, w = (e) => {
|
|
15
|
+
const l = " ", s = /(>)(<)(\/*)/g;
|
|
16
|
+
let o = e.replace(s, `$1
|
|
17
|
+
$2$3`), n = 0;
|
|
18
|
+
return o.split(`
|
|
19
|
+
`).map((r) => {
|
|
20
|
+
let a = 0;
|
|
21
|
+
/^<\/\w/.test(r) ? n = Math.max(n - 1, 0) : /^<\w[^>]*[^/]>.*$/.test(r) && !/<.+<\/.+>$/.test(r) && (a = 1);
|
|
22
|
+
const i = l.repeat(n) + r;
|
|
23
|
+
return n += a, i;
|
|
24
|
+
}).join(`
|
|
25
|
+
`);
|
|
26
|
+
}, S = ({ url: e }) => {
|
|
27
|
+
const l = m(), [s, o] = p(""), [n, r] = p(!0), [a, i] = p(null), { html: c } = h(s, "xml");
|
|
28
|
+
return d(() => {
|
|
29
|
+
(async () => {
|
|
30
|
+
try {
|
|
31
|
+
r(!0), i(null);
|
|
32
|
+
const f = await u(e);
|
|
33
|
+
o(g(f));
|
|
34
|
+
} catch (f) {
|
|
35
|
+
console.error(f), i(l("xml.load_failed"));
|
|
36
|
+
} finally {
|
|
37
|
+
r(!1);
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
}, [e]), n ? /* @__PURE__ */ t("div", { className: "rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full", children: /* @__PURE__ */ t("div", { className: "rfp-w-12 rfp-h-12 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin" }) }) : a ? /* @__PURE__ */ t("div", { className: "rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full", children: /* @__PURE__ */ t("div", { className: "rfp-text-fg-secondary rfp-text-center", children: /* @__PURE__ */ t("p", { className: "rfp-text-lg", children: a }) }) }) : /* @__PURE__ */ t("div", { className: "rfp-w-full rfp-h-full rfp-overflow-auto rfp-bg-code-bg", children: c ? /* @__PURE__ */ t(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: "rfp-shiki-wrapper with-line-numbers",
|
|
44
|
+
dangerouslySetInnerHTML: { __html: c }
|
|
45
|
+
}
|
|
46
|
+
) : /* @__PURE__ */ t("pre", { className: "rfp-p-6 rfp-text-fg-primary rfp-font-mono rfp-text-sm rfp-whitespace-pre-wrap rfp-break-words", children: s }) });
|
|
47
|
+
};
|
|
48
|
+
export {
|
|
49
|
+
S as XmlRenderer
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=index-B5tBeF0g.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-B5tBeF0g.mjs","sources":["../../src/renderers/Xml/index.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { fetchTextUtf8 } from '@eternalheart/file-preview-core';\nimport { useTranslator } from '../../i18n/LocaleContext';\nimport { useShikiHighlight } from '../../hooks/useShikiHighlight';\n\ninterface XmlRendererProps {\n url: string;\n fileName: string;\n}\n\n/**\n * 用 DOMParser 美化 XML:失败则原样返回\n */\nconst prettyPrintXml = (xml: string): string => {\n try {\n const parser = new DOMParser();\n const doc = parser.parseFromString(xml, 'application/xml');\n // 检测解析错误\n const errNode = doc.querySelector('parsererror');\n if (errNode) return xml;\n // 使用 XSLT 或手动缩进:这里手动缩进更稳\n const serializer = new XMLSerializer();\n const serialized = serializer.serializeToString(doc);\n return indentXml(serialized);\n } catch {\n return xml;\n }\n};\n\nconst indentXml = (xml: string): string => {\n const PADDING = ' ';\n const reg = /(>)(<)(\\/*)/g;\n let formatted = xml.replace(reg, '$1\\n$2$3');\n // 自闭合和 CDATA 等不处理\n let pad = 0;\n return formatted\n .split('\\n')\n .map((line) => {\n let indent = 0;\n if (/^<\\/\\w/.test(line)) {\n pad = Math.max(pad - 1, 0);\n } else if (/^<\\w[^>]*[^/]>.*$/.test(line) && !/<.+<\\/.+>$/.test(line)) {\n indent = 1;\n }\n const padded = PADDING.repeat(pad) + line;\n pad += indent;\n return padded;\n })\n .join('\\n');\n};\n\nexport const XmlRenderer: React.FC<XmlRendererProps> = ({ url }) => {\n const t = useTranslator();\n const [content, setContent] = useState<string>('');\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const { html: highlighted } = useShikiHighlight(content, 'xml');\n\n useEffect(() => {\n const load = async () => {\n try {\n setLoading(true);\n setError(null);\n const raw = await fetchTextUtf8(url);\n setContent(prettyPrintXml(raw));\n } catch (err) {\n console.error(err);\n setError(t('xml.load_failed'));\n } finally {\n setLoading(false);\n }\n };\n load();\n }, [url]);\n\n if (loading) {\n return (\n <div className=\"rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full\">\n <div className=\"rfp-w-12 rfp-h-12 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full\">\n <div className=\"rfp-text-fg-secondary rfp-text-center\">\n <p className=\"rfp-text-lg\">{error}</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"rfp-w-full rfp-h-full rfp-overflow-auto rfp-bg-code-bg\">\n {highlighted ? (\n <div\n className=\"rfp-shiki-wrapper with-line-numbers\"\n dangerouslySetInnerHTML={{ __html: highlighted }}\n />\n ) : (\n <pre className=\"rfp-p-6 rfp-text-fg-primary rfp-font-mono rfp-text-sm rfp-whitespace-pre-wrap rfp-break-words\">\n {content}\n </pre>\n )}\n </div>\n );\n};\n"],"names":["prettyPrintXml","xml","doc","serialized","indentXml","PADDING","reg","formatted","pad","line","indent","padded","XmlRenderer","url","t","useTranslator","content","setContent","useState","loading","setLoading","error","setError","highlighted","useShikiHighlight","useEffect","raw","fetchTextUtf8","err","jsx"],"mappings":";;;;AAaA,MAAMA,IAAiB,CAACC,MAAwB;AAC9C,MAAI;AAEF,UAAMC,IADS,IAAI,UAAA,EACA,gBAAgBD,GAAK,iBAAiB;AAGzD,QADgBC,EAAI,cAAc,aAAa,EAClC,QAAOD;AAGpB,UAAME,IADa,IAAI,cAAA,EACO,kBAAkBD,CAAG;AACnD,WAAOE,EAAUD,CAAU;AAAA,EAC7B,QAAQ;AACN,WAAOF;AAAA,EACT;AACF,GAEMG,IAAY,CAACH,MAAwB;AACzC,QAAMI,IAAU,MACVC,IAAM;AACZ,MAAIC,IAAYN,EAAI,QAAQK,GAAK;AAAA,KAAU,GAEvCE,IAAM;AACV,SAAOD,EACJ,MAAM;AAAA,CAAI,EACV,IAAI,CAACE,MAAS;AACb,QAAIC,IAAS;AACb,IAAI,SAAS,KAAKD,CAAI,IACpBD,IAAM,KAAK,IAAIA,IAAM,GAAG,CAAC,IAChB,oBAAoB,KAAKC,CAAI,KAAK,CAAC,aAAa,KAAKA,CAAI,MAClEC,IAAS;AAEX,UAAMC,IAASN,EAAQ,OAAOG,CAAG,IAAIC;AACrC,WAAAD,KAAOE,GACAC;AAAA,EACT,CAAC,EACA,KAAK;AAAA,CAAI;AACd,GAEaC,IAA0C,CAAC,EAAE,KAAAC,QAAU;AAClE,QAAMC,IAAIC,EAAA,GACJ,CAACC,GAASC,CAAU,IAAIC,EAAiB,EAAE,GAC3C,CAACC,GAASC,CAAU,IAAIF,EAAS,EAAI,GACrC,CAACG,GAAOC,CAAQ,IAAIJ,EAAwB,IAAI,GAChD,EAAE,MAAMK,EAAA,IAAgBC,EAAkBR,GAAS,KAAK;AAmB9D,SAjBAS,EAAU,MAAM;AAcd,KAba,YAAY;AACvB,UAAI;AACF,QAAAL,EAAW,EAAI,GACfE,EAAS,IAAI;AACb,cAAMI,IAAM,MAAMC,EAAcd,CAAG;AACnC,QAAAI,EAAWjB,EAAe0B,CAAG,CAAC;AAAA,MAChC,SAASE,GAAK;AACZ,gBAAQ,MAAMA,CAAG,GACjBN,EAASR,EAAE,iBAAiB,CAAC;AAAA,MAC/B,UAAA;AACE,QAAAM,EAAW,EAAK;AAAA,MAClB;AAAA,IACF,GACA;AAAA,EACF,GAAG,CAACP,CAAG,CAAC,GAEJM,IAEA,gBAAAU,EAAC,SAAI,WAAU,sEACb,4BAAC,OAAA,EAAI,WAAU,qHAAoH,EAAA,CACrI,IAIAR,IAEA,gBAAAQ,EAAC,OAAA,EAAI,WAAU,sEACb,4BAAC,OAAA,EAAI,WAAU,yCACb,UAAA,gBAAAA,EAAC,KAAA,EAAE,WAAU,eAAe,UAAAR,EAAA,CAAM,GACpC,GACF,IAKF,gBAAAQ,EAAC,OAAA,EAAI,WAAU,0DACZ,UAAAN,IACC,gBAAAM;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACV,yBAAyB,EAAE,QAAQN,EAAA;AAAA,IAAY;AAAA,EAAA,IAGjD,gBAAAM,EAAC,OAAA,EAAI,WAAU,iGACZ,aACH,GAEJ;AAEJ;"}
|