Greasy Fork

Greasy Fork is available in English.

ChatGPT 渲染HTML代码

chatgpt渲染html代码块,像Claude一样实时预览html ( chatgpt renders HTML code blocks, previewing HTML in real-time like Claude )

当前为 2024-10-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         ChatGPT 渲染HTML代码
// @namespace    https://github.com/21888/chatgpt-render-html
// @version      2024-10-07
// @description  chatgpt渲染html代码块,像Claude一样实时预览html ( chatgpt renders HTML code blocks, previewing HTML in real-time like Claude )
// @author       chabai
// @match        https://chatgpt.com/c/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @grant        none
// @license      GPL-2.0-only
// ==/UserScript==
(function () {
    "use strict";
    // 使用 MutationObserver 监听 DOM 变化
    const observer = new MutationObserver(debounce(xuanranHTML, 500));
    const createIframeObserver = new MutationObserver(debounce(createIframe, 500));

    // 观察目标节点的变化
    observer.observe(document.body, { childList: true, subtree: true });
    createIframeObserver.observe(document.body, { childList: true, subtree: true });

    // 首次调用渲染
    window.addEventListener("load", () => {
        setTimeout(xuanranHTML, 1000);
        // 创建一个iframe
        setTimeout(createIframe, 1000);
    });
})();

function createIframe() {
    // 判断是否已经创建 dynamicContentIframe
    if (document.getElementById("dynamicContentIframe")) {
        console.log("已经创建 dynamicContentIframe");
        return;
    }

    // 创建一个基于main标签的兄弟iframe元素
    const mainElement = document.querySelector("main");
    // mainElement.style.display = "flex";
    mainElement.style.overflow = "hidden";
    if (mainElement) {
        const iframe = document.createElement("iframe");
        iframe.id = "dynamicContentIframe"; // 添加id以便动态修改内容
        iframe.style.display = "relative";
        iframe.style.width = "100%";
        iframe.style.height = "100%";
        iframe.style.backgroundColor = "#FFFDF6"; // 添加奶白色背景

        // 创建切换显示代码块的div
        const toggleCodeButton = document.createElement("div");
        toggleCodeButton.id = "toggleCodeButton";
        toggleCodeButton.style.position = "absolute";
        toggleCodeButton.style.top = "10px";
        toggleCodeButton.style.right = "40px";
        toggleCodeButton.style.cursor = "pointer";
        toggleCodeButton.innerHTML = `
            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M4 6H16M4 10H16M4 14H16" stroke="black" stroke-width="2" stroke-linecap="round"/>
            </svg>
        `;
        toggleCodeButton.onclick = () => {
            // document.getElementById("dynamicContentIframe").contentWindow.document.body.innerHTML = "123";
            document.getElementById("codeContainer").style.display =
                document.getElementById("codeContainer").style.display ===
                "none"
                    ? "block"
                    : "none";
        };

        // 创建缩小按钮
        const minimizeButton = document.createElement("div");
        minimizeButton.id = "minimizeButton";
        minimizeButton.style.position = "absolute";
        minimizeButton.style.top = "10px";
        minimizeButton.style.right = "10px";
        minimizeButton.style.cursor = "pointer";
        minimizeButton.innerHTML = `
            <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M4 10H16" stroke="black" stroke-width="2" stroke-linecap="round"/>
            </svg>
        `;
        minimizeButton.onclick = () => {
            iframe.style.display =
                iframe.style.display === "none" ? "block" : "none";
            document.querySelector("main").style.display =
                document.querySelector("main").style.display === "block"
                    ? "flex"
                    : "block";
            document
                .querySelector(
                    "body > div.relative.flex.h-full.w-full.overflow-hidden.transition-colors.z-0 > div.flex-shrink-0.overflow-x-hidden.bg-token-sidebar-surface-primary.max-md\\:\\!w-0 > div > div > div > nav > div.flex.justify-between.flex.h-\\[60px\\].items-center.md\\:h-header-height > span > button"
                )
                .click();
        };
        //  toggleCodeButton 和 minimizeButton 添加到一个div下
        const buttonContainer = document.createElement("div");
        buttonContainer.id = "buttonContainer";
        buttonContainer.style.position = "relative";
        buttonContainer.style.top = "10px";
        buttonContainer.style.right = "10px";
        buttonContainer.appendChild(minimizeButton);
        buttonContainer.appendChild(toggleCodeButton);

        // 直接将 iframe 和按钮添加到 main 元素下
        mainElement.appendChild(iframe);
        mainElement.appendChild(buttonContainer);

        // 设置 main 元素为相对定位,以便正确定位最小化按钮
        mainElement.style.position = "relative";

        // mainElement.insertBefore(container, mainElement.nextSibling);
        // 创建一个用户存放显示代码的div
        const codeContainer = document.createElement("div");
        codeContainer.id = "codeContainer";
        codeContainer.style.width = "100%";
        codeContainer.style.height = "100%";
        codeContainer.style.zIndex = "1000";
        // codeContainer.style.backgroundColor = "#FFFDF6"; // 添加奶白色背景
        codeContainer.style.display = "block";
        codeContainer.style.overflow = "hidden";
        codeContainer.style.overflowY = "scroll";
        codeContainer.style.display = "none";
        mainElement.appendChild(codeContainer);
    } else {
        console.error("Main element not found");
    }
    // 显示
    document.getElementById("dynamicContentIframe").style.display = "block";
}
function xuanranHTML() {
    // 获取所有的 code 元素
    const codes = document.querySelectorAll(".overflow-y-auto.p-4 code");

    // 遍历所有的 code 元素
    codes.forEach((codeElement) => {
        // 检查是否已经处理过,避免重复插入
        if (codeElement.classList.contains("processed")) {
            return;
        }

        // 标记该元素已经处理过
        codeElement.classList.add("processed");

        // 判断 codeElement.textContent 是否是 HTML 内容
        if (codeElement.parentNode.parentNode.children[0].innerText != "html") {
            console.log(
                "不渲染: " +
                    codeElement.parentNode.parentNode.children[0].innerText
            );
            return;
        }
        // 隐藏codeElement
        codeElement.parentNode.parentNode.style.display = "none";
        let componentContainer = renderSmallWindow(codeElement);
    });
}

function renderSmallWindow(codeElement) {
    // 创建组件容器
    const componentContainer = document.createElement("div");
    componentContainer.style.display = "flex";
    componentContainer.style.alignItems = "center";
    componentContainer.style.border = "1px solid #e5e7eb";
    componentContainer.style.borderRadius = "8px";
    componentContainer.style.padding = "10px";
    componentContainer.style.backgroundColor = "#f9fafb";
    componentContainer.style.marginBottom = "20px";
    componentContainer.style.cursor = "pointer";

    // 创建图标容器
    const iconContainer = document.createElement("div");
    iconContainer.style.borderRight = "1px solid #e5e7eb";
    iconContainer.style.paddingRight = "10px";

    // 创建 SVG 图标
    const svgIcon = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "svg"
    );
    svgIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    svgIcon.setAttribute("style", "height: 24px; width: 24px; color: #6b7280;");
    svgIcon.setAttribute("fill", "none");
    svgIcon.setAttribute("viewBox", "0 0 24 24");
    svgIcon.setAttribute("stroke", "currentColor");
    svgIcon.setAttribute("stroke-width", "2");

    const pathElement = document.createElementNS(
        "http://www.w3.org/2000/svg",
        "path"
    );
    pathElement.setAttribute("stroke-linecap", "round");
    pathElement.setAttribute("stroke-linejoin", "round");
    pathElement.setAttribute(
        "d",
        "M16 8c0-1.104-.9-2-2-2H6c-1.1 0-2 .896-2 2v8c0 1.104.9 2 2 2h8c1.1 0 2-.896 2-2V8zm-4 4h.01m-3 0h.01M7 12h.01m0 0h.01M12 7v.01M8 7v.01M7 7v.01M12 8v.01m-5 0v.01m-1 0v.01"
    );

    svgIcon.appendChild(pathElement);
    iconContainer.appendChild(svgIcon);

    // 创建文本容器
    const textContainer = document.createElement("div");
    textContainer.style.marginLeft = "10px";

    // 创建标题
    const title = document.createElement("h3");
    title.textContent = codeElement.parentNode.parentNode.children[0].innerText;
    title.style.margin = "0";
    title.style.fontSize = "16px";
    title.style.fontWeight = "600";
    title.style.color = "#374151";

    // 创建描述
    const description = document.createElement("p");
    description.textContent = "预览页面 / 刷新渲染";
    description.style.margin = "0";
    description.style.fontSize = "14px";
    description.style.color = "#6b7280";

    textContainer.appendChild(title);
    textContainer.appendChild(description);

    // 组装组件
    componentContainer.appendChild(iconContainer);
    componentContainer.appendChild(textContainer);

    // 添加点击事件,切换代码显示状态
    componentContainer.addEventListener("click", function () {
        const codeParent = codeElement.parentNode.parentNode;
        const mainElement = document.querySelector("main");
        // codeParent.style.display = codeParent.style.display === 'none' ? 'block' : 'none';
        // 隐藏展开左侧历史记录
        if (mainElement.style.display != "flex") {
            mainElement.style.display = "flex";
        }
        if (
            document.querySelector(
                "body > div.relative.flex.h-full.w-full.overflow-hidden.transition-colors.z-0 > div.flex-shrink-0.overflow-x-hidden.bg-token-sidebar-surface-primary.max-md\\:\\!w-0"
            ).style.width != "0px"
        ) {
            document
                .querySelector(
                    "body > div.relative.flex.h-full.w-full.overflow-hidden.transition-colors.z-0 > div.flex-shrink-0.overflow-x-hidden.bg-token-sidebar-surface-primary.max-md\\:\\!w-0 > div > div > div > nav > div.flex.justify-between.flex.h-\\[60px\\].items-center.md\\:h-header-height > span > button"
                )
                .click();
            // dynamicContentIframe
            document.getElementById("dynamicContentIframe").style.display =
                "block";
        }

        // iframe 赋值
        renderIframeContent(
            document.getElementById("dynamicContentIframe"),
            codeElement.textContent
        );
        // 设置代码容器代码
        // document.getElementById("codeContainer").innerHTML = codeElement.parentNode.parentNode.innerHTML;
        const codeContainer = document.getElementById("codeContainer");
        codeContainer.innerHTML = "";
        const clonedContent = codeElement.parentNode.parentNode.cloneNode(true);
        codeContainer.appendChild(clonedContent);
        codeContainer.childNodes[0].style.display = "contents";

        // 查找并处理按钮
        const buttons = clonedContent.querySelectorAll("button");
        buttons.forEach((button, index) => {
            console.log(button);
            if (button.className == "flex gap-1 items-center py-1") {
                button.addEventListener("click", function (event) {
                    button.innerText = " success √ ";
                    setTimeout(() => {
                        button.innerText = "Copy code";
                    }, 1000);
                });
            }
            button.addEventListener("click", function (event) {
                event.stopPropagation(); // 阻止事件冒泡到父元素
                // 找到原始按钮并模拟点击
                const originalButtons =
                    codeElement.parentNode.parentNode.querySelectorAll(
                        "button"
                    );
                if (originalButtons[index]) {
                    originalButtons[index].click();
                }
            });
        });

        // 添加事件委托到 codeContainer(用于处理代码元素的点击)
        codeContainer.addEventListener("click", function (event) {
            const clickedElement = event.target.closest("code");
            if (clickedElement) {
                // 模拟原始代码元素的点击行为
                const originalCodeElement = codeElement.closest("code");
                if (originalCodeElement) {
                    originalCodeElement.click();
                }
            }
        });
    });

    // 将组件插入到代码元素之前
    codeElement.parentNode.parentNode.insertAdjacentElement(
        "beforebegin",
        componentContainer
    );
    return componentContainer;
}
function renderIframeContent(iframe, content) {
    // 获取 iframe 内部的 document 对象
    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

    // 将 code 元素的内容设置为 iframe 的内容
    iframeDoc.open();
    if (true) {
        // 刷新特效,true为有感刷新,!true为无痕刷新
        iframeDoc.write(`loading`);
        setTimeout(() => iframeDoc.write(content), 100);
    } else {
        iframeDoc.write(content);
    }
    iframeDoc.close();
}

function debounce(func, wait) {
    let timeout;
    return function () {
        const context = this,
            args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(context, args), wait);
    };
}