Skip to content

Commit ffe3269

Browse files
authored
Merge pull request #5300 from ConnectAI-E/hotfix/hide-button
Hotfix/hide button
2 parents a6b14c7 + 3d5b211 commit ffe3269

File tree

3 files changed

+121
-76
lines changed

3 files changed

+121
-76
lines changed

app/components/artifacts.tsx

Lines changed: 92 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { useEffect, useState, useRef, useMemo } from "react";
1+
import {
2+
useEffect,
3+
useState,
4+
useRef,
5+
useMemo,
6+
forwardRef,
7+
useImperativeHandle,
8+
} from "react";
29
import { useParams } from "react-router";
310
import { useWindowSize } from "@/app/utils";
411
import { IconButton } from "./button";
@@ -8,80 +15,97 @@ import CopyIcon from "../icons/copy.svg";
815
import DownloadIcon from "../icons/download.svg";
916
import GithubIcon from "../icons/github.svg";
1017
import LoadingButtonIcon from "../icons/loading.svg";
18+
import ReloadButtonIcon from "../icons/reload.svg";
1119
import Locale from "../locales";
1220
import { Modal, showToast } from "./ui-lib";
1321
import { copyToClipboard, downloadAs } from "../utils";
1422
import { Path, ApiPath, REPO_URL } from "@/app/constant";
1523
import { Loading } from "./home";
1624
import styles from "./artifacts.module.scss";
1725

18-
export function HTMLPreview(props: {
26+
type HTMLPreviewProps = {
1927
code: string;
2028
autoHeight?: boolean;
2129
height?: number | string;
2230
onLoad?: (title?: string) => void;
23-
}) {
24-
const ref = useRef<HTMLIFrameElement>(null);
25-
const frameId = useRef<string>(nanoid());
26-
const [iframeHeight, setIframeHeight] = useState(600);
27-
const [title, setTitle] = useState("");
28-
/*
29-
* https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
30-
* 1. using srcdoc
31-
* 2. using src with dataurl:
32-
* easy to share
33-
* length limit (Data URIs cannot be larger than 32,768 characters.)
34-
*/
31+
};
3532

36-
useEffect(() => {
37-
const handleMessage = (e: any) => {
38-
const { id, height, title } = e.data;
39-
setTitle(title);
40-
if (id == frameId.current) {
41-
setIframeHeight(height);
42-
}
43-
};
44-
window.addEventListener("message", handleMessage);
45-
return () => {
46-
window.removeEventListener("message", handleMessage);
47-
};
48-
}, []);
33+
export type HTMLPreviewHander = {
34+
reload: () => void;
35+
};
4936

50-
const height = useMemo(() => {
51-
if (!props.autoHeight) return props.height || 600;
52-
if (typeof props.height === "string") {
53-
return props.height;
54-
}
55-
const parentHeight = props.height || 600;
56-
return iframeHeight + 40 > parentHeight ? parentHeight : iframeHeight + 40;
57-
}, [props.autoHeight, props.height, iframeHeight]);
37+
export const HTMLPreview = forwardRef<HTMLPreviewHander, HTMLPreviewProps>(
38+
function HTMLPreview(props, ref) {
39+
const iframeRef = useRef<HTMLIFrameElement>(null);
40+
const [frameId, setFrameId] = useState<string>(nanoid());
41+
const [iframeHeight, setIframeHeight] = useState(600);
42+
const [title, setTitle] = useState("");
43+
/*
44+
* https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
45+
* 1. using srcdoc
46+
* 2. using src with dataurl:
47+
* easy to share
48+
* length limit (Data URIs cannot be larger than 32,768 characters.)
49+
*/
5850

59-
const srcDoc = useMemo(() => {
60-
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId.current}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
61-
if (props.code.includes("</head>")) {
62-
props.code.replace("</head>", "</head>" + script);
63-
}
64-
return props.code + script;
65-
}, [props.code]);
51+
useEffect(() => {
52+
const handleMessage = (e: any) => {
53+
const { id, height, title } = e.data;
54+
setTitle(title);
55+
if (id == frameId) {
56+
setIframeHeight(height);
57+
}
58+
};
59+
window.addEventListener("message", handleMessage);
60+
return () => {
61+
window.removeEventListener("message", handleMessage);
62+
};
63+
}, [frameId]);
6664

67-
const handleOnLoad = () => {
68-
if (props?.onLoad) {
69-
props.onLoad(title);
70-
}
71-
};
65+
useImperativeHandle(ref, () => ({
66+
reload: () => {
67+
setFrameId(nanoid());
68+
},
69+
}));
7270

73-
return (
74-
<iframe
75-
className={styles["artifacts-iframe"]}
76-
id={frameId.current}
77-
ref={ref}
78-
sandbox="allow-forms allow-modals allow-scripts"
79-
style={{ height }}
80-
srcDoc={srcDoc}
81-
onLoad={handleOnLoad}
82-
/>
83-
);
84-
}
71+
const height = useMemo(() => {
72+
if (!props.autoHeight) return props.height || 600;
73+
if (typeof props.height === "string") {
74+
return props.height;
75+
}
76+
const parentHeight = props.height || 600;
77+
return iframeHeight + 40 > parentHeight
78+
? parentHeight
79+
: iframeHeight + 40;
80+
}, [props.autoHeight, props.height, iframeHeight]);
81+
82+
const srcDoc = useMemo(() => {
83+
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
84+
if (props.code.includes("</head>")) {
85+
props.code.replace("</head>", "</head>" + script);
86+
}
87+
return props.code + script;
88+
}, [props.code, frameId]);
89+
90+
const handleOnLoad = () => {
91+
if (props?.onLoad) {
92+
props.onLoad(title);
93+
}
94+
};
95+
96+
return (
97+
<iframe
98+
className={styles["artifacts-iframe"]}
99+
key={frameId}
100+
ref={iframeRef}
101+
sandbox="allow-forms allow-modals allow-scripts"
102+
style={{ height }}
103+
srcDoc={srcDoc}
104+
onLoad={handleOnLoad}
105+
/>
106+
);
107+
},
108+
);
85109

86110
export function ArtifactsShareButton({
87111
getCode,
@@ -184,6 +208,7 @@ export function Artifacts() {
184208
const [code, setCode] = useState("");
185209
const [loading, setLoading] = useState(true);
186210
const [fileName, setFileName] = useState("");
211+
const previewRef = useRef<HTMLPreviewHander>(null);
187212

188213
useEffect(() => {
189214
if (id) {
@@ -208,6 +233,13 @@ export function Artifacts() {
208233
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
209234
<IconButton bordered icon={<GithubIcon />} shadow />
210235
</a>
236+
<IconButton
237+
bordered
238+
style={{ marginLeft: 20 }}
239+
icon={<ReloadButtonIcon />}
240+
shadow
241+
onClick={() => previewRef.current?.reload()}
242+
/>
211243
<div className={styles["artifacts-title"]}>NextChat Artifacts</div>
212244
<ArtifactsShareButton
213245
id={id}
@@ -220,6 +252,7 @@ export function Artifacts() {
220252
{code && (
221253
<HTMLPreview
222254
code={code}
255+
ref={previewRef}
223256
autoHeight={false}
224257
height={"100%"}
225258
onLoad={(title) => {

app/components/markdown.tsx

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@ import { copyToClipboard, useWindowSize } from "../utils";
1010
import mermaid from "mermaid";
1111

1212
import LoadingIcon from "../icons/three-dots.svg";
13+
import ReloadButtonIcon from "../icons/reload.svg";
1314
import React from "react";
1415
import { useDebouncedCallback } from "use-debounce";
1516
import { showImageModal, FullScreen } from "./ui-lib";
16-
import { ArtifactsShareButton, HTMLPreview } from "./artifacts";
17+
import {
18+
ArtifactsShareButton,
19+
HTMLPreview,
20+
HTMLPreviewHander,
21+
} from "./artifacts";
1722
import { Plugin } from "../constant";
1823
import { useChatStore } from "../store";
24+
import { IconButton } from "./button";
25+
1926
export function Mermaid(props: { code: string }) {
2027
const ref = useRef<HTMLDivElement>(null);
2128
const [hasError, setHasError] = useState(false);
@@ -64,7 +71,7 @@ export function Mermaid(props: { code: string }) {
6471

6572
export function PreCode(props: { children: any }) {
6673
const ref = useRef<HTMLPreElement>(null);
67-
const refText = ref.current?.innerText;
74+
const previewRef = useRef<HTMLPreviewHander>(null);
6875
const [mermaidCode, setMermaidCode] = useState("");
6976
const [htmlCode, setHtmlCode] = useState("");
7077
const { height } = useWindowSize();
@@ -79,18 +86,14 @@ export function PreCode(props: { children: any }) {
7986
setMermaidCode((mermaidDom as HTMLElement).innerText);
8087
}
8188
const htmlDom = ref.current.querySelector("code.language-html");
89+
const refText = ref.current.querySelector("code")?.innerText;
8290
if (htmlDom) {
8391
setHtmlCode((htmlDom as HTMLElement).innerText);
8492
} else if (refText?.startsWith("<!DOCTYPE")) {
8593
setHtmlCode(refText);
8694
}
8795
}, 600);
8896

89-
useEffect(() => {
90-
setTimeout(renderArtifacts, 1);
91-
// eslint-disable-next-line react-hooks/exhaustive-deps
92-
}, [refText]);
93-
9497
const enableArtifacts = useMemo(
9598
() => plugins?.includes(Plugin.Artifacts),
9699
[plugins],
@@ -119,6 +122,7 @@ export function PreCode(props: { children: any }) {
119122
codeElement.style.whiteSpace = "pre-wrap";
120123
}
121124
});
125+
setTimeout(renderArtifacts, 1);
122126
}
123127
}, []);
124128

@@ -145,7 +149,15 @@ export function PreCode(props: { children: any }) {
145149
style={{ position: "absolute", right: 20, top: 10 }}
146150
getCode={() => htmlCode}
147151
/>
152+
<IconButton
153+
style={{ position: "absolute", right: 120, top: 10 }}
154+
bordered
155+
icon={<ReloadButtonIcon />}
156+
shadow
157+
onClick={() => previewRef.current?.reload()}
158+
/>
148159
<HTMLPreview
160+
ref={previewRef}
149161
code={htmlCode}
150162
autoHeight={!document.fullscreenElement}
151163
height={!document.fullscreenElement ? 600 : height}
@@ -182,16 +194,14 @@ function CustomCode(props: { children: any }) {
182194
}}
183195
>
184196
{props.children}
185-
{showToggle && collapsed && (
186-
<div
187-
className={`show-hide-button ${
188-
collapsed ? "collapsed" : "expanded"
189-
}`}
190-
>
191-
<button onClick={toggleCollapsed}>查看全部</button>
192-
</div>
193-
)}
194197
</code>
198+
{showToggle && collapsed && (
199+
<div
200+
className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}
201+
>
202+
<button onClick={toggleCollapsed}>查看全部</button>
203+
</div>
204+
)}
195205
</>
196206
);
197207
}

app/styles/globals.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ pre {
304304
}
305305
}
306306

307-
code{
307+
pre {
308308
.show-hide-button {
309309
border-radius: 10px;
310310
position: absolute;
@@ -314,7 +314,9 @@ code{
314314
height: fit-content;
315315
display: inline-flex;
316316
justify-content: center;
317+
pointer-events: none;
317318
button{
319+
pointer-events: auto;
318320
margin-top: 3em;
319321
margin-bottom: 4em;
320322
padding: 5px 16px;

0 commit comments

Comments
 (0)