feat(frontend): 接入 pdf.js 预览并调整产物预览逻辑
This commit is contained in:
parent
3d38501cd5
commit
b7ead65f1d
|
|
@ -81,6 +81,7 @@
|
|||
"nextra-theme-docs": "^4.6.1",
|
||||
"nuxt-og-image": "^5.1.13",
|
||||
"ogl": "^1.0.11",
|
||||
"pdfjs-dist": "^5.6.205",
|
||||
"pptx-preview": "^1.0.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
|
|
|||
|
|
@ -191,6 +191,9 @@ importers:
|
|||
ogl:
|
||||
specifier: ^1.0.11
|
||||
version: 1.0.11
|
||||
pdfjs-dist:
|
||||
specifier: ^5.6.205
|
||||
version: 5.6.205
|
||||
pptx-preview:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7
|
||||
|
|
@ -963,6 +966,77 @@ packages:
|
|||
'@mermaid-js/parser@0.6.3':
|
||||
resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==}
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.97':
|
||||
resolution: {integrity: sha512-V1c/WVw+NzH8vk7ZK/O8/nyBSCQimU8sfMsB/9qeSvdkGKNU7+mxy/bIF0gTgeBFmHpj30S4E9WHMSrxXGQuVQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.97':
|
||||
resolution: {integrity: sha512-ok+SCEF4YejcxuJ9Rm+WWunHHpf2HmiPxfz6z1a/NFQECGXtsY7A4B8XocK1LmT1D7P174MzwPF9Wy3AUAwEPw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.97':
|
||||
resolution: {integrity: sha512-PUP6e6/UGlclUvAQNnuXCcnkpdUou6VYZfQOQxExLp86epOylmiwLkqXIvpFmjoTEDmPmXrI+coL/9EFU1gKPA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.97':
|
||||
resolution: {integrity: sha512-XyXH2L/cic8eTNtbrXCcvqHtMX/nEOxN18+7rMrAM2XtLYC/EB5s0wnO1FsLMWmK+04ZSLN9FBGipo7kpIkcOw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.97':
|
||||
resolution: {integrity: sha512-Kuq/M3djq0K8ktgz6nPlK7Ne5d4uWeDxPpyKWOjWDK2RIOhHVtLtyLiJw2fuldw7Vn4mhw05EZXCEr4Q76rs9w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.97':
|
||||
resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.97':
|
||||
resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.97':
|
||||
resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.97':
|
||||
resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.97':
|
||||
resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.97':
|
||||
resolution: {integrity: sha512-sWtD2EE3fV0IzN+iiQUqr/Q1SwqWhs2O1FKItFlxtdDkikpEj5g7DKQpY3x55H/MAOnL8iomnlk3mcEeGiUMoQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@napi-rs/canvas@0.1.97':
|
||||
resolution: {integrity: sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@napi-rs/simple-git-android-arm-eabi@0.1.22':
|
||||
resolution: {integrity: sha512-JQZdnDNm8o43A5GOzwN/0Tz3CDBQtBUNqzVwEopm32uayjdjxev1Csp1JeaqF3v9djLDIvsSE39ecsN2LhCKKQ==}
|
||||
engines: {node: '>= 10'}
|
||||
|
|
@ -4605,6 +4679,9 @@ packages:
|
|||
node-mock-http@1.0.4:
|
||||
resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==}
|
||||
|
||||
node-readable-to-web-readable-stream@0.4.2:
|
||||
resolution: {integrity: sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==}
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -4789,6 +4866,10 @@ packages:
|
|||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
pdfjs-dist@5.6.205:
|
||||
resolution: {integrity: sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==}
|
||||
engines: {node: '>=20.19.0 || >=22.13.0 || >=24'}
|
||||
|
||||
perfect-debounce@2.1.0:
|
||||
resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==}
|
||||
|
||||
|
|
@ -6751,6 +6832,54 @@ snapshots:
|
|||
dependencies:
|
||||
langium: 3.3.1
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-win32-arm64-msvc@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.97':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas@0.1.97':
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas-android-arm64': 0.1.97
|
||||
'@napi-rs/canvas-darwin-arm64': 0.1.97
|
||||
'@napi-rs/canvas-darwin-x64': 0.1.97
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.97
|
||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.97
|
||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.97
|
||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.97
|
||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.97
|
||||
'@napi-rs/canvas-linux-x64-musl': 0.1.97
|
||||
'@napi-rs/canvas-win32-arm64-msvc': 0.1.97
|
||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.97
|
||||
optional: true
|
||||
|
||||
'@napi-rs/simple-git-android-arm-eabi@0.1.22':
|
||||
optional: true
|
||||
|
||||
|
|
@ -10952,6 +11081,9 @@ snapshots:
|
|||
|
||||
node-mock-http@1.0.4: {}
|
||||
|
||||
node-readable-to-web-readable-stream@0.4.2:
|
||||
optional: true
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
npm-run-path@5.3.0:
|
||||
|
|
@ -11205,6 +11337,11 @@ snapshots:
|
|||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
pdfjs-dist@5.6.205:
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas': 0.1.97
|
||||
node-readable-to-web-readable-stream: 0.4.2
|
||||
|
||||
perfect-debounce@2.1.0: {}
|
||||
|
||||
performance-now@2.1.0:
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ export function ArtifactFileDetail({
|
|||
}, [filepath]);
|
||||
const artifactViewerSrcDoc = useMemo(() => {
|
||||
if (!artifactUrl) {
|
||||
return "";
|
||||
return undefined;
|
||||
}
|
||||
return buildArtifactViewerSrcDoc({
|
||||
artifactUrl,
|
||||
|
|
@ -120,6 +120,11 @@ export function ArtifactFileDetail({
|
|||
kind: artifactPreviewKind,
|
||||
});
|
||||
}, [artifactUrl, fileName, artifactPreviewKind]);
|
||||
// Native PDF iframe rendering is intentionally disabled; PDFs are rendered via pdf.js.
|
||||
const artifactViewerSrc = useMemo(() => {
|
||||
return undefined;
|
||||
}, []);
|
||||
const artifactViewerSandbox = "allow-same-origin allow-scripts allow-downloads";
|
||||
const { content } = useArtifactContent({
|
||||
threadId,
|
||||
filepath: filepathFromProps,
|
||||
|
|
@ -608,7 +613,13 @@ export function ArtifactFileDetail({
|
|||
</div>
|
||||
)}
|
||||
{!isCodeFile && (
|
||||
isOfficePreviewKind(artifactPreviewKind) ? (
|
||||
artifactPreviewKind === "pdf" ? (
|
||||
<ArtifactPdfPreview
|
||||
className="h-full mb-[207px]"
|
||||
artifactUrl={artifactUrl}
|
||||
fileName={fileName}
|
||||
/>
|
||||
) : isOfficePreviewKind(artifactPreviewKind) ? (
|
||||
<ArtifactOfficePreview
|
||||
className="h-full mb-[207px]"
|
||||
kind={artifactPreviewKind}
|
||||
|
|
@ -619,8 +630,9 @@ export function ArtifactFileDetail({
|
|||
<PreviewIframe
|
||||
className="size-full border-0"
|
||||
containerClassName="h-full mb-[207px]"
|
||||
src={artifactViewerSrc}
|
||||
srcDoc={artifactViewerSrcDoc}
|
||||
sandbox="allow-same-origin allow-scripts allow-downloads"
|
||||
sandbox={artifactViewerSandbox}
|
||||
title={`Artifact preview: ${fileName}`}
|
||||
/>
|
||||
)
|
||||
|
|
@ -841,6 +853,144 @@ function PreviewIframe({
|
|||
);
|
||||
}
|
||||
|
||||
function ArtifactPdfPreview({
|
||||
className,
|
||||
artifactUrl,
|
||||
fileName,
|
||||
}: {
|
||||
className?: string;
|
||||
artifactUrl: string;
|
||||
fileName: string;
|
||||
}) {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pageCount, setPageCount] = useState(0);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
|
||||
async function renderPdf() {
|
||||
if (!artifactUrl || !containerRef.current) return;
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setPageCount(0);
|
||||
containerRef.current.innerHTML = "";
|
||||
|
||||
try {
|
||||
const response = await fetch(artifactUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
const data = await response.arrayBuffer();
|
||||
|
||||
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||
"pdfjs-dist/legacy/build/pdf.worker.min.mjs",
|
||||
import.meta.url,
|
||||
).toString();
|
||||
|
||||
const loadingTask = pdfjs.getDocument({ data });
|
||||
const pdf = await loadingTask.promise;
|
||||
if (disposed || !containerRef.current) {
|
||||
await loadingTask.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
setPageCount(pdf.numPages);
|
||||
|
||||
const hostWidth = Math.max(
|
||||
640,
|
||||
Math.min(containerRef.current.clientWidth || 960, 1200),
|
||||
);
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
|
||||
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum += 1) {
|
||||
if (disposed || !containerRef.current) break;
|
||||
|
||||
const page = await pdf.getPage(pageNum);
|
||||
const baseViewport = page.getViewport({ scale: 1 });
|
||||
const scale = (hostWidth - 32) / baseViewport.width;
|
||||
const viewport = page.getViewport({ scale });
|
||||
|
||||
const pageWrapper = document.createElement("div");
|
||||
pageWrapper.className =
|
||||
"mx-auto mb-4 w-fit rounded-md border border-[#e4e7ec] bg-white p-2 shadow-sm";
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.width = `${viewport.width}px`;
|
||||
canvas.style.height = `${viewport.height}px`;
|
||||
canvas.width = Math.floor(viewport.width * dpr);
|
||||
canvas.height = Math.floor(viewport.height * dpr);
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) continue;
|
||||
context.scale(dpr, dpr);
|
||||
|
||||
pageWrapper.appendChild(canvas);
|
||||
containerRef.current.appendChild(pageWrapper);
|
||||
|
||||
const renderTask = page.render({
|
||||
canvas,
|
||||
canvasContext: context,
|
||||
viewport,
|
||||
});
|
||||
await renderTask.promise;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to render pdf preview:", err);
|
||||
if (!disposed) {
|
||||
setError("无法预览该 PDF 文件,请下载后查看。");
|
||||
}
|
||||
} finally {
|
||||
if (!disposed) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void renderPdf();
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [artifactUrl]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={cn("relative overflow-auto bg-[#f8f9fb] p-4", className)}>
|
||||
<div className="mx-auto grid max-w-xl gap-3 rounded-md border border-[#e4e7ec] bg-white p-5 text-center">
|
||||
<p className="text-sm font-medium break-all">{fileName}</p>
|
||||
<p className="text-muted-foreground text-sm">{error}</p>
|
||||
<a
|
||||
className="text-sm font-semibold text-blue-600 hover:underline"
|
||||
href={artifactUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
在新标签页打开
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("relative overflow-auto bg-[#f8f9fb] p-4", className)}>
|
||||
<div className="mb-3 text-center text-xs text-[#667085]">
|
||||
{pageCount > 0 ? `${fileName} · ${pageCount} page(s)` : fileName}
|
||||
</div>
|
||||
<div ref={containerRef} />
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center bg-white/70">
|
||||
<LoaderIcon className="text-muted-foreground size-5 animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ArtifactOfficePreview({
|
||||
className,
|
||||
kind,
|
||||
|
|
@ -858,7 +1008,6 @@ function ArtifactOfficePreview({
|
|||
const [sheetNames, setSheetNames] = useState<string[]>([]);
|
||||
const [activeSheet, setActiveSheet] = useState<string>("");
|
||||
const docxContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const pptxContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const workbookRef = useRef<XLSX.WorkBook | null>(null);
|
||||
|
||||
const canRenderDocx = kind === "docx";
|
||||
|
|
@ -972,54 +1121,12 @@ function ArtifactOfficePreview({
|
|||
}
|
||||
}, [activeSheet, canRenderXlsx]);
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
|
||||
type PptxPreviewModule = {
|
||||
init: (
|
||||
container: HTMLElement,
|
||||
options: { width: number; height: number },
|
||||
) => {
|
||||
preview: (buffer: ArrayBuffer) => Promise<void> | void;
|
||||
};
|
||||
};
|
||||
|
||||
async function renderPptx() {
|
||||
if (!canRenderPptx || !artifactUrl || !pptxContainerRef.current) {
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch(artifactUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
const bytes = await response.arrayBuffer();
|
||||
const pptxModule = (await import("pptx-preview")) as unknown as PptxPreviewModule;
|
||||
if (disposed || !pptxContainerRef.current) {
|
||||
return;
|
||||
}
|
||||
const container = pptxContainerRef.current;
|
||||
container.innerHTML = "";
|
||||
const previewer = pptxModule.init(container, { width: 960, height: 540 });
|
||||
await Promise.resolve(previewer.preview(bytes));
|
||||
} catch (err) {
|
||||
console.error("Failed to render pptx preview:", err);
|
||||
if (!disposed) {
|
||||
setError("无法预览该 PPT 文件。");
|
||||
}
|
||||
} finally {
|
||||
if (!disposed) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
if (!canRenderPptx) {
|
||||
return;
|
||||
}
|
||||
|
||||
void renderPptx();
|
||||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, [artifactUrl, canRenderPptx]);
|
||||
setIsLoading(false);
|
||||
setError("请下载ppt文件以获得最佳效果");
|
||||
}, [canRenderPptx]);
|
||||
|
||||
return (
|
||||
<div className={cn("relative h-full overflow-hidden bg-white", className)}>
|
||||
|
|
@ -1056,12 +1163,6 @@ function ArtifactOfficePreview({
|
|||
dangerouslySetInnerHTML={{ __html: xlsxHtml }}
|
||||
/>
|
||||
)}
|
||||
{canRenderPptx && (
|
||||
<div
|
||||
ref={pptxContainerRef}
|
||||
className="pptx-preview-wrap mx-auto w-full overflow-auto"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
|
@ -1099,7 +1200,7 @@ function ArtifactPreviewFallback({
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
在新标签页打开
|
||||
点击下载
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1239,7 +1340,11 @@ function buildArtifactViewerSrcDoc({
|
|||
return `<div class="audio-wrap"><audio class="audio" src="${safeUrl}" controls preload="metadata"></audio></div>`;
|
||||
}
|
||||
if (kind === "pdf") {
|
||||
return `<iframe class="preview frame" src="${safeUrl}#view=FitH"></iframe>`;
|
||||
return `<div class="fallback">
|
||||
<p class="title">${safeName}</p>
|
||||
<p class="desc">PDF preview is temporarily disabled. Please download the file to view it.</p>
|
||||
<a class="link" href="${safeUrl}" target="_blank" rel="noopener noreferrer">Open in new tab</a>
|
||||
</div>`;
|
||||
}
|
||||
if (kind === "html") {
|
||||
return `<iframe class="preview frame" src="${safeUrl}" sandbox="allow-scripts allow-forms allow-modals allow-popups allow-downloads"></iframe>`;
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ async function waitAfterCardClick(page: Page, kind: string): Promise<void> {
|
|||
return;
|
||||
}
|
||||
if (kind === "pptx") {
|
||||
await expect(page.locator(".pptx-preview-wrap").first()).toBeVisible({
|
||||
await expect(page.getByText("请下载ppt文件以获得最佳效果").first()).toBeVisible({
|
||||
timeout: 60_000,
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue