巴哈姆特Dx

整合圖片懸浮顯示留言、展開留言功能

安裝腳本?
作者推薦腳本

您可能也會喜歡 巴哈姆特

以使用者樣式安裝
// ==UserScript==
// @name         巴哈姆特Dx
// @namespace    https://forum.gamer.com.tw/
// @version      2025.1021
// @description  整合圖片懸浮顯示留言、展開留言功能
// @match        https://forum.gamer.com.tw/C.php?*
// @match        https://forum.gamer.com.tw/Co.php?*
// @match        https://forum.gamer.com.tw/G2.php?*
// @require      https://unpkg.com/popper.js@1
// @require      https://unpkg.com/tippy.js@5
// @resource     TIPPY_LIGHT_THEME https://unpkg.com/tippy.js@6/themes/light.css
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT
// 1021應該只是修復留言懸浮顯示的bug其它沒有改變
// ==/UserScript==

(function () {
    'use strict';

    let overlay, enlargedImg, activeImg, hoverTimeout, replyMap = new Map();
    const abortController = new AbortController();

    const tippyLightTheme = GM_getResourceText("TIPPY_LIGHT_THEME");
    if (tippyLightTheme) {
        GM_addStyle(tippyLightTheme);
    }
    GM_addStyle(`.c-post.c-section__main img { image-rendering: -webkit-optimize-contrast; }`);

    function createOverlay() {
        if (overlay) return; // 防止重复创建
        overlay = document.createElement('div');
        Object.assign(overlay.style, {
            position: 'fixed',
            top: 0,
            left: 0,
            width: '100%',
            height: '100%',
            display: 'none',
            justifyContent: 'center',
            alignItems: 'center',
            zIndex: '9999',
            pointerEvents: 'none'
        });
        document.body.appendChild(overlay);

        enlargedImg = document.createElement('img');
        Object.assign(enlargedImg.style, {
            maxWidth: '99vw',
            maxHeight: '99vh',
            objectFit: 'contain',
            pointerEvents: 'none'
        });
        overlay.appendChild(enlargedImg);
    }

    function showEnlargedImage(img) {
        createOverlay(); // 确保覆盖层存在
        const rawSrc = img.src.split('?')[0];
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        let displayWidth = img.naturalWidth;
        let displayHeight = img.naturalHeight;

        if (img.naturalWidth > viewportWidth || img.naturalHeight > viewportHeight) {
            const imgAspectRatio = img.naturalWidth / img.naturalHeight;
            if (imgAspectRatio > viewportWidth / viewportHeight) {
                displayWidth = viewportWidth * 0.99;
                displayHeight = displayWidth / imgAspectRatio;
            } else {
                displayHeight = viewportHeight * 0.99;
                displayWidth = displayHeight * imgAspectRatio;
            }
        }

        enlargedImg.src = rawSrc;
        enlargedImg.style.width = `${displayWidth}px`;
        enlargedImg.style.height = `${displayHeight}px`;
        overlay.style.display = 'flex';
    }

    function hideEnlargedImage() {
        if (overlay) {
            overlay.style.display = 'none';
        }
    }

    function processImage(img) {
        if (img.dataset.processed) return;

        const excludePattern = /(editor\/emotion|avataruserpic)/;
        if (excludePattern.test(img.src)) return;

        img.dataset.processed = 'true'; // 使用字符串
        img.style.cursor = 'zoom-in';

        if (img.src.includes('?')) {
            const rawSrc = img.src.split('?')[0];
            img.dataset.originalSrc = img.src;
            img.src = rawSrc;
        }

        const handler = function (e) {
            clearTimeout(hoverTimeout);
            if (e.type === 'mouseenter') {
                activeImg = img;
                hoverTimeout = setTimeout(() => showEnlargedImage(img), 100);
            } else if (e.type === 'mouseleave') {
                if (activeImg === img) {
                    hoverTimeout = setTimeout(() => {
                        hideEnlargedImage();
                        activeImg = null;
                    }, 100);
                }
            }
        };

        img.addEventListener('mouseenter', handler);
        img.addEventListener('mouseleave', handler);
    }

    function checkForNewImages() {
        document.querySelectorAll('.c-post.c-section__main img:not([data-processed])').forEach(img => {
            if (img.complete && img.naturalHeight > 0) {
                processImage(img);
            } else {
                const loadHandler = () => {
                    processImage(img);
                    img.removeEventListener('load', loadHandler);
                };
                img.addEventListener('load', loadHandler);
            }
        });
    }

    function waitElement(selector) {
        return new Promise(resolve => {
            const element = document.querySelector(selector);
            if (element) return resolve(element);
            const observer = new MutationObserver(() => {
                const element = document.querySelector(selector);
                if (element) {
                    observer.disconnect();
                    resolve(element);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

    function addReplyMouseoverListener(node) {
        const commentEles = new Set();
        node.querySelectorAll('.reply-content a[href^="javascript:Forum.C.openCommentDialog"]').forEach(replyEle => {
            const commentEle = replyEle.closest(".c-reply__item");
            if (commentEle && !commentEles.has(commentEle)) {
                commentEles.add(commentEle);
                addReplyTooltip(commentEle);
            }
        });
    }

    function addReplyTooltip(commentEle) {
        commentEle.querySelectorAll('a[href^="javascript:Forum.C.openCommentDialog"]').forEach(replyEle => {
            const matches = replyEle.getAttribute("href").match(/\d+/g);
            if (!matches || matches.length < 3) return; // 确保有足够的匹配项
            const [bsn, snB, snC] = matches;

            getReplyApiEle(bsn, snB, snC).then(replyApiEle => {
                if (!replyEle) return; // 确保元素仍然存在

                // 设置 tippy.js 配置
                replyEle.setAttribute('data-tippy-content', `<div class="dialogify" style="display: contents; text-align:left;"><div class="dialogify__body">${replyApiEle}</div></div>`);
                replyEle.setAttribute('data-tippy-theme', 'light'); // 默认主题
                replyEle.setAttribute('data-tippy-allowHTML', 'true');
                replyEle.setAttribute('data-tippy-interactive', 'true');
                replyEle.setAttribute('data-tippy-placement', 'top');

                const mouseHandler = () => {
                    if (!replyEle._tippyInstance) {
                        replyEle._tippyInstance = tippy(replyEle, {
                            maxWidth: 560,
                            interactive: true,
                            allowHTML: true,
                            appendTo: document.body,
                            theme: document.querySelector("html").dataset.theme === 'dark' ? 'dark' : 'light',
                            onShow(instance) {
                                // 在显示时可能需要再次格式化内容,如果Forum.C.commentFormatter依赖于弹出内容的DOM
                                // 但此脚本中 Forum.C.commentFormatter 可能无法在此处正确应用,因为内容已在tippy内部
                                // 原始代码试图在onShow中应用,但可能无效。这里保留逻辑,但可能需要调整。
                                const contentSpan = instance.popper.querySelector("span");
                                if (contentSpan && typeof Forum?.C?.commentFormatter === 'function') {
                                    Forum.C.commentFormatter(contentSpan);
                                }
                            },
                            onHidden(instance) {
                                // 不自动销毁实例,因为可能频繁悬停
                                // instance.destroy();
                            }
                        });
                    }
                    // 移除一次性事件监听器
                    replyEle.removeEventListener('mouseover', mouseHandler);
                };
                replyEle.addEventListener('mouseover', mouseHandler, { once: true });
            }).catch(error => {
                console.warn("Failed to fetch reply tooltip for snC:", snC, error);
            });
        });
    }

    function getReplyApiEle(bsn, snB, snC) {
        return new Promise((resolve, reject) => {
            if (replyMap.size > 50) replyMap.clear();
            if (replyMap.has(snC)) return resolve(replyMap.get(snC));

            fetch(`https://api.gamer.com.tw/forum/v1/comment_get.php?bsn=${bsn}&snB=${snB}&snC=${snC}&type=pc`, {
                credentials: "include",
                signal: abortController.signal
            })
            .then(res => {
                if (!res.ok) {
                    throw new Error(`HTTP error! status: ${res.status}`);
                }
                return res.json();
            })
            .then(json => {
                if (json && json.data && json.data.comment && typeof json.data.comment.html === 'string') {
                    replyMap.set(snC, json.data.comment.html);
                    resolve(replyMap.get(snC));
                } else {
                    throw new Error("Invalid API response structure");
                }
            })
            .catch(error => {
                if (error.name !== 'AbortError') {
                    console.error("Fetch error in getReplyApiEle:", error);
                    reject(error); // 传递错误,以便调用者可以处理
                } else {
                    reject(error); // 即使是 AbortError 也传递,让调用者知道
                }
            });
        });
    }

    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        checkForNewImages();
                        // 为新添加的节点也添加回复监听
                        addReplyMouseoverListener(node);
                    }
                });
            }
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });
    checkForNewImages();

    const checkInterval = setInterval(checkForNewImages, 3000);

    window.addEventListener('beforeunload', () => {
        observer.disconnect();
        clearInterval(checkInterval);
        abortController.abort();
        // 恢复图片原始链接
        document.querySelectorAll('img[data-original-src]').forEach(img => {
            if (img.dataset.originalSrc) {
                img.src = img.dataset.originalSrc;
                delete img.dataset.originalSrc;
            }
            delete img.dataset.processed;
        });
        // 清理可能存在的 tippy 实例
        document.querySelectorAll('a[data-tippy-content]').forEach(el => {
            if (el._tippyInstance) {
                el._tippyInstance.destroy();
            }
        });
    });

    switch (window.location.host) {
        case "forum.gamer.com.tw":
            // 确保 DOM 加载完成后再执行
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', () => {
                    document.querySelectorAll("[id^=showoldCommend_]").forEach(el => el.click());
                    document.querySelectorAll("[id^=Commendlist]").forEach(commentList => {
                        const listObserver = new MutationObserver(mutations => {
                            mutations.forEach(mutation => {
                                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                                    mutation.addedNodes.forEach(node => {
                                        if (node.nodeType === Node.ELEMENT_NODE) {
                                            addReplyMouseoverListener(node);
                                        }
                                    });
                                }
                            });
                        });
                        listObserver.observe(commentList, { childList: true });
                    });
                });
            } else {
                document.querySelectorAll("[id^=showoldCommend_]").forEach(el => el.click());
                document.querySelectorAll("[id^=Commendlist]").forEach(commentList => {
                    const listObserver = new MutationObserver(mutations => {
                        mutations.forEach(mutation => {
                            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                                mutation.addedNodes.forEach(node => {
                                    if (node.nodeType === Node.ELEMENT_NODE) {
                                        addReplyMouseoverListener(node);
                                    }
                                });
                            }
                        });
                    });
                    listObserver.observe(commentList, { childList: true });
                });
            }
            break;
        case "gnn.gamer.com.tw":
            waitElement('#comment a[href^="javascript:get_all_comment"]').then(e => {
                if (e && e.getAttribute('href')) {
                    const funcStr = e.getAttribute('href').split(':')[1];
                    if (funcStr) {
                        new Function(funcStr)();
                    }
                }
            }).catch(console.error); // 捕获 waitElement 的错误
            break;
    }

    addReplyMouseoverListener(document);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址