mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-05-23 14:10:18 +09:00
Merge pull request #5300 from ConnectAI-E/hotfix/hide-button
Hotfix/hide button
This commit is contained in:
commit
ffe32694b0
@ -1,4 +1,11 @@
|
|||||||
import { useEffect, useState, useRef, useMemo } from "react";
|
import {
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
useMemo,
|
||||||
|
forwardRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
} from "react";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { useWindowSize } from "@/app/utils";
|
import { useWindowSize } from "@/app/utils";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
@ -8,6 +15,7 @@ import CopyIcon from "../icons/copy.svg";
|
|||||||
import DownloadIcon from "../icons/download.svg";
|
import DownloadIcon from "../icons/download.svg";
|
||||||
import GithubIcon from "../icons/github.svg";
|
import GithubIcon from "../icons/github.svg";
|
||||||
import LoadingButtonIcon from "../icons/loading.svg";
|
import LoadingButtonIcon from "../icons/loading.svg";
|
||||||
|
import ReloadButtonIcon from "../icons/reload.svg";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { Modal, showToast } from "./ui-lib";
|
import { Modal, showToast } from "./ui-lib";
|
||||||
import { copyToClipboard, downloadAs } from "../utils";
|
import { copyToClipboard, downloadAs } from "../utils";
|
||||||
@ -15,73 +23,89 @@ import { Path, ApiPath, REPO_URL } from "@/app/constant";
|
|||||||
import { Loading } from "./home";
|
import { Loading } from "./home";
|
||||||
import styles from "./artifacts.module.scss";
|
import styles from "./artifacts.module.scss";
|
||||||
|
|
||||||
export function HTMLPreview(props: {
|
type HTMLPreviewProps = {
|
||||||
code: string;
|
code: string;
|
||||||
autoHeight?: boolean;
|
autoHeight?: boolean;
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
onLoad?: (title?: string) => void;
|
onLoad?: (title?: string) => void;
|
||||||
}) {
|
};
|
||||||
const ref = useRef<HTMLIFrameElement>(null);
|
|
||||||
const frameId = useRef<string>(nanoid());
|
|
||||||
const [iframeHeight, setIframeHeight] = useState(600);
|
|
||||||
const [title, setTitle] = useState("");
|
|
||||||
/*
|
|
||||||
* https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
|
|
||||||
* 1. using srcdoc
|
|
||||||
* 2. using src with dataurl:
|
|
||||||
* easy to share
|
|
||||||
* length limit (Data URIs cannot be larger than 32,768 characters.)
|
|
||||||
*/
|
|
||||||
|
|
||||||
useEffect(() => {
|
export type HTMLPreviewHander = {
|
||||||
const handleMessage = (e: any) => {
|
reload: () => void;
|
||||||
const { id, height, title } = e.data;
|
};
|
||||||
setTitle(title);
|
|
||||||
if (id == frameId.current) {
|
export const HTMLPreview = forwardRef<HTMLPreviewHander, HTMLPreviewProps>(
|
||||||
setIframeHeight(height);
|
function HTMLPreview(props, ref) {
|
||||||
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
|
const [frameId, setFrameId] = useState<string>(nanoid());
|
||||||
|
const [iframeHeight, setIframeHeight] = useState(600);
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
/*
|
||||||
|
* https://stackoverflow.com/questions/19739001/what-is-the-difference-between-srcdoc-and-src-datatext-html-in-an
|
||||||
|
* 1. using srcdoc
|
||||||
|
* 2. using src with dataurl:
|
||||||
|
* easy to share
|
||||||
|
* length limit (Data URIs cannot be larger than 32,768 characters.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (e: any) => {
|
||||||
|
const { id, height, title } = e.data;
|
||||||
|
setTitle(title);
|
||||||
|
if (id == frameId) {
|
||||||
|
setIframeHeight(height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("message", handleMessage);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("message", handleMessage);
|
||||||
|
};
|
||||||
|
}, [frameId]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
reload: () => {
|
||||||
|
setFrameId(nanoid());
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const height = useMemo(() => {
|
||||||
|
if (!props.autoHeight) return props.height || 600;
|
||||||
|
if (typeof props.height === "string") {
|
||||||
|
return props.height;
|
||||||
|
}
|
||||||
|
const parentHeight = props.height || 600;
|
||||||
|
return iframeHeight + 40 > parentHeight
|
||||||
|
? parentHeight
|
||||||
|
: iframeHeight + 40;
|
||||||
|
}, [props.autoHeight, props.height, iframeHeight]);
|
||||||
|
|
||||||
|
const srcDoc = useMemo(() => {
|
||||||
|
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
|
||||||
|
if (props.code.includes("</head>")) {
|
||||||
|
props.code.replace("</head>", "</head>" + script);
|
||||||
|
}
|
||||||
|
return props.code + script;
|
||||||
|
}, [props.code, frameId]);
|
||||||
|
|
||||||
|
const handleOnLoad = () => {
|
||||||
|
if (props?.onLoad) {
|
||||||
|
props.onLoad(title);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener("message", handleMessage);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("message", handleMessage);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const height = useMemo(() => {
|
return (
|
||||||
if (!props.autoHeight) return props.height || 600;
|
<iframe
|
||||||
if (typeof props.height === "string") {
|
className={styles["artifacts-iframe"]}
|
||||||
return props.height;
|
key={frameId}
|
||||||
}
|
ref={iframeRef}
|
||||||
const parentHeight = props.height || 600;
|
sandbox="allow-forms allow-modals allow-scripts"
|
||||||
return iframeHeight + 40 > parentHeight ? parentHeight : iframeHeight + 40;
|
style={{ height }}
|
||||||
}, [props.autoHeight, props.height, iframeHeight]);
|
srcDoc={srcDoc}
|
||||||
|
onLoad={handleOnLoad}
|
||||||
const srcDoc = useMemo(() => {
|
/>
|
||||||
const script = `<script>new ResizeObserver((entries) => parent.postMessage({id: '${frameId.current}', height: entries[0].target.clientHeight}, '*')).observe(document.body)</script>`;
|
);
|
||||||
if (props.code.includes("</head>")) {
|
},
|
||||||
props.code.replace("</head>", "</head>" + script);
|
);
|
||||||
}
|
|
||||||
return props.code + script;
|
|
||||||
}, [props.code]);
|
|
||||||
|
|
||||||
const handleOnLoad = () => {
|
|
||||||
if (props?.onLoad) {
|
|
||||||
props.onLoad(title);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<iframe
|
|
||||||
className={styles["artifacts-iframe"]}
|
|
||||||
id={frameId.current}
|
|
||||||
ref={ref}
|
|
||||||
sandbox="allow-forms allow-modals allow-scripts"
|
|
||||||
style={{ height }}
|
|
||||||
srcDoc={srcDoc}
|
|
||||||
onLoad={handleOnLoad}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ArtifactsShareButton({
|
export function ArtifactsShareButton({
|
||||||
getCode,
|
getCode,
|
||||||
@ -184,6 +208,7 @@ export function Artifacts() {
|
|||||||
const [code, setCode] = useState("");
|
const [code, setCode] = useState("");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [fileName, setFileName] = useState("");
|
const [fileName, setFileName] = useState("");
|
||||||
|
const previewRef = useRef<HTMLPreviewHander>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
@ -208,6 +233,13 @@ export function Artifacts() {
|
|||||||
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
||||||
<IconButton bordered icon={<GithubIcon />} shadow />
|
<IconButton bordered icon={<GithubIcon />} shadow />
|
||||||
</a>
|
</a>
|
||||||
|
<IconButton
|
||||||
|
bordered
|
||||||
|
style={{ marginLeft: 20 }}
|
||||||
|
icon={<ReloadButtonIcon />}
|
||||||
|
shadow
|
||||||
|
onClick={() => previewRef.current?.reload()}
|
||||||
|
/>
|
||||||
<div className={styles["artifacts-title"]}>NextChat Artifacts</div>
|
<div className={styles["artifacts-title"]}>NextChat Artifacts</div>
|
||||||
<ArtifactsShareButton
|
<ArtifactsShareButton
|
||||||
id={id}
|
id={id}
|
||||||
@ -220,6 +252,7 @@ export function Artifacts() {
|
|||||||
{code && (
|
{code && (
|
||||||
<HTMLPreview
|
<HTMLPreview
|
||||||
code={code}
|
code={code}
|
||||||
|
ref={previewRef}
|
||||||
autoHeight={false}
|
autoHeight={false}
|
||||||
height={"100%"}
|
height={"100%"}
|
||||||
onLoad={(title) => {
|
onLoad={(title) => {
|
||||||
|
@ -10,12 +10,19 @@ import { copyToClipboard, useWindowSize } from "../utils";
|
|||||||
import mermaid from "mermaid";
|
import mermaid from "mermaid";
|
||||||
|
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
|
import ReloadButtonIcon from "../icons/reload.svg";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { showImageModal, FullScreen } from "./ui-lib";
|
import { showImageModal, FullScreen } from "./ui-lib";
|
||||||
import { ArtifactsShareButton, HTMLPreview } from "./artifacts";
|
import {
|
||||||
|
ArtifactsShareButton,
|
||||||
|
HTMLPreview,
|
||||||
|
HTMLPreviewHander,
|
||||||
|
} from "./artifacts";
|
||||||
import { Plugin } from "../constant";
|
import { Plugin } from "../constant";
|
||||||
import { useChatStore } from "../store";
|
import { useChatStore } from "../store";
|
||||||
|
import { IconButton } from "./button";
|
||||||
|
|
||||||
export function Mermaid(props: { code: string }) {
|
export function Mermaid(props: { code: string }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [hasError, setHasError] = useState(false);
|
const [hasError, setHasError] = useState(false);
|
||||||
@ -64,7 +71,7 @@ export function Mermaid(props: { code: string }) {
|
|||||||
|
|
||||||
export function PreCode(props: { children: any }) {
|
export function PreCode(props: { children: any }) {
|
||||||
const ref = useRef<HTMLPreElement>(null);
|
const ref = useRef<HTMLPreElement>(null);
|
||||||
const refText = ref.current?.innerText;
|
const previewRef = useRef<HTMLPreviewHander>(null);
|
||||||
const [mermaidCode, setMermaidCode] = useState("");
|
const [mermaidCode, setMermaidCode] = useState("");
|
||||||
const [htmlCode, setHtmlCode] = useState("");
|
const [htmlCode, setHtmlCode] = useState("");
|
||||||
const { height } = useWindowSize();
|
const { height } = useWindowSize();
|
||||||
@ -79,6 +86,7 @@ export function PreCode(props: { children: any }) {
|
|||||||
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
setMermaidCode((mermaidDom as HTMLElement).innerText);
|
||||||
}
|
}
|
||||||
const htmlDom = ref.current.querySelector("code.language-html");
|
const htmlDom = ref.current.querySelector("code.language-html");
|
||||||
|
const refText = ref.current.querySelector("code")?.innerText;
|
||||||
if (htmlDom) {
|
if (htmlDom) {
|
||||||
setHtmlCode((htmlDom as HTMLElement).innerText);
|
setHtmlCode((htmlDom as HTMLElement).innerText);
|
||||||
} else if (refText?.startsWith("<!DOCTYPE")) {
|
} else if (refText?.startsWith("<!DOCTYPE")) {
|
||||||
@ -86,11 +94,6 @@ export function PreCode(props: { children: any }) {
|
|||||||
}
|
}
|
||||||
}, 600);
|
}, 600);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeout(renderArtifacts, 1);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [refText]);
|
|
||||||
|
|
||||||
const enableArtifacts = useMemo(
|
const enableArtifacts = useMemo(
|
||||||
() => plugins?.includes(Plugin.Artifacts),
|
() => plugins?.includes(Plugin.Artifacts),
|
||||||
[plugins],
|
[plugins],
|
||||||
@ -119,6 +122,7 @@ export function PreCode(props: { children: any }) {
|
|||||||
codeElement.style.whiteSpace = "pre-wrap";
|
codeElement.style.whiteSpace = "pre-wrap";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setTimeout(renderArtifacts, 1);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -145,7 +149,15 @@ export function PreCode(props: { children: any }) {
|
|||||||
style={{ position: "absolute", right: 20, top: 10 }}
|
style={{ position: "absolute", right: 20, top: 10 }}
|
||||||
getCode={() => htmlCode}
|
getCode={() => htmlCode}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
style={{ position: "absolute", right: 120, top: 10 }}
|
||||||
|
bordered
|
||||||
|
icon={<ReloadButtonIcon />}
|
||||||
|
shadow
|
||||||
|
onClick={() => previewRef.current?.reload()}
|
||||||
|
/>
|
||||||
<HTMLPreview
|
<HTMLPreview
|
||||||
|
ref={previewRef}
|
||||||
code={htmlCode}
|
code={htmlCode}
|
||||||
autoHeight={!document.fullscreenElement}
|
autoHeight={!document.fullscreenElement}
|
||||||
height={!document.fullscreenElement ? 600 : height}
|
height={!document.fullscreenElement ? 600 : height}
|
||||||
@ -182,16 +194,14 @@ function CustomCode(props: { children: any }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
{showToggle && collapsed && (
|
|
||||||
<div
|
|
||||||
className={`show-hide-button ${
|
|
||||||
collapsed ? "collapsed" : "expanded"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<button onClick={toggleCollapsed}>查看全部</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</code>
|
</code>
|
||||||
|
{showToggle && collapsed && (
|
||||||
|
<div
|
||||||
|
className={`show-hide-button ${collapsed ? "collapsed" : "expanded"}`}
|
||||||
|
>
|
||||||
|
<button onClick={toggleCollapsed}>查看全部</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,7 @@ pre {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code{
|
pre {
|
||||||
.show-hide-button {
|
.show-hide-button {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -314,7 +314,9 @@ code{
|
|||||||
height: fit-content;
|
height: fit-content;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
button{
|
button{
|
||||||
|
pointer-events: auto;
|
||||||
margin-top: 3em;
|
margin-top: 3em;
|
||||||
margin-bottom: 4em;
|
margin-bottom: 4em;
|
||||||
padding: 5px 16px;
|
padding: 5px 16px;
|
||||||
|
Loading…
Reference in New Issue
Block a user