游戏社区(TapTap)列表页贴子预览

TapTap游戏社区列表页贴子卡片新增预览按钮,可在列表页直接预览贴子内容。

目前为 2025-02-15 提交的版本。查看 最新版本

// ==UserScript==
// @name            游戏社区(TapTap)列表页贴子预览
// @namespace       游戏社区(TapTap)列表页贴子预览
// @license         GPL-3.0 License
// @version         1.3.3
// @description     TapTap游戏社区列表页贴子卡片新增预览按钮,可在列表页直接预览贴子内容。
// @author          QIAN
// @match           *://www.taptap.cn/app/*/topic*
// @grant           GM_addStyle
// @grant           GM_info
// @require         https://scriptcat.org/lib/513/2.0.1/ElementGetter.js?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg=
// @supportURL      https://github.com/QIUZAIYOU/Taptap-PostPreview
// @homepageURL     https://github.com/QIUZAIYOU/Taptap-PostPreview
// @icon            https://assets.tapimg.com/cupid-apps/web-app/favicon.2.ico
// ==/UserScript==
// ?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg=
(function () {
    'use strict';
    const selector = {
        tap: '#__nuxt',
        momentCardFooter: '.moment-card__footer',
        previewWrapper: '#previewWrapper',
        previewIframe: '#previewIframe',
        previewIframeMask: '#previewIframeMask',
        previewContentHeader: 'header',
        previewContentMain: 'main',
        previewButton: '.previewButton',
        voteButton: '.vote-button.moment-card__footer-button',
    }
    const styles = {
        PreviewWrapperStyle: '#previewWrapper{position:fixed;top:50%;left:50%;overflow:hidden;box-sizing:border-box;width:600px;height:97vh;border:2px solid #00d9c5;border-radius:10px;background:#191919;transform:translate(-50%,-50%)}#previewWrapper::backdrop{backdrop-filter:blur(3px)}#previewIframe{width:100%;height:100%;border:none;background:#191919}#previewIframeMask{position:absolute;display:flex;background:#191919;inset:0;align-items:center;justify-content:center}.previewButton{cursor: pointer}',
        PreviewIframeStyle: 'body{background:#191919!important}header{display:none!important}main{margin-left:0!important}'
    }
    const utils = {
        /**
         * 休眠
         * @param {Number} 时长
         * @returns
         */
        sleep(times) {
            return new Promise(resolve => setTimeout(resolve, times))
        },
        logger: {
            info(content) {
                console.info('%cTapTap预览', 'color:white;background:#006aff;padding:2px;border-radius:2px', content);
            },
            warn(content) {
                console.warn('%cTapTap贴子预览', 'color:white;background:#ff6d00;padding:2px;border-radius:2px', content);
            },
            error(content) {
                console.error('%cTapTap贴子预览', 'color:white;background:#f33;padding:2px;border-radius:2px', content);
            },
            debug(content) {
                console.info('%cTapTap贴子预览(调试)', 'color:white;background:#cc00ff;padding:2px;border-radius:2px', content);
            },
        },
        createElementAndInsert(HtmlString, target, method) {
            const element = elmGetter.create(HtmlString, target)
            target[method](element)
            return element
        },
        insertStyleToDocument(id, css) {
            const styleElement = GM_addStyle(css)
            styleElement.id = id
        },
        htmlStringToDom(htmlString) {
            const tempDiv = document.createElement('div');
            tempDiv.innerHTML = htmlString.trim();
            return tempDiv.firstChild;
        },
        isAsyncFunction(targetFunction) {
            return targetFunction.constructor.name === 'AsyncFunction'
        },
        reloadCurrentTab(...args) {
            if (args && args[0] === true) {
                location.reload()
            } else if (vals.auto_reload()) location.reload()
        },
        executeFunctionsSequentially(functionsArray) {
            if (functionsArray.length > 0) {
                const currentFunction = functionsArray.shift()
                if (utils.isAsyncFunction(currentFunction)) {
                    currentFunction().then(result => {
                        if (result) {
                            const { message, callback } = result
                            if (message) utils.logger.info(message)
                            if (callback && Array.isArray(callback)) utils.executeFunctionsSequentially(callback)
                        }
                        utils.executeFunctionsSequentially(functionsArray)
                    }).catch(error => {
                        utils.logger.error(error)
                        utils.reloadCurrentTab()
                    })
                } else {
                    const result = currentFunction()
                    if (result) {
                        const { message } = result
                        if (message) utils.logger.info(message)
                    }
                }
            }
        },
        checkElementExistence(elementsArray) {
            if (Array.isArray(elementsArray)) {
                return elementsArray.map(element => Boolean(element))
            } else {
                return [Boolean(elementsArray)]
            }
        },
        async getElementAndCheckExistence(selectors, ...args) {
            let delay = 7000, debug = false
            if (args.length === 1) {
                const type = typeof args[0]
                if (type === 'number') delay = args[0]
                if (type === 'boolean') debug = args[0]
            }
            if (args.length === 2) {
                delay = args[0]
                debug = args[1]
            }
            const result = await elmGetter.get(selectors, delay)
            if (debug) utils.logger.debug(utils.checkElementExistence(result))
            return result
        },
    }
    const modules = {
        async insertPreviewElementToDocument() {
            const existingPreviewWrapper = await utils.getElementAndCheckExistence(selector.previewWrapper);
            if (existingPreviewWrapper) existingPreviewWrapper.remove();

            const previewElementHtml = `
                <div id="previewWrapper" popover>
                    <div id="perViewFloat">
                        <div id="previewClose"></div>
                        <div id="previewEnterPost"><a href=""></a></div>
                    </div>
                    <div id="previewIframeMask">
                        <div class="loading-dots__wrapper" type="dots" loading="true">
                            <span class="loading-dots__dot" style="font-size: 6px;"></span>
                            <span class="loading-dots__dot" style="font-size: 6px;"></span>
                            <span class="loading-dots__dot" style="font-size: 6px;"></span>
                        </div>
                    </div>
                    <iframe id="previewIframe" title="previewIframe"></iframe>
                </div>
            `;
            const previewWrapper = utils.createElementAndInsert(previewElementHtml, document.body, 'append');
            const previewIframe = previewWrapper.querySelector(selector.previewIframe);
            const previewIframeMask = previewWrapper.querySelector(selector.previewIframeMask);
            const previewIframeWindow = previewIframe.contentWindow;

            previewWrapper.addEventListener('toggle', (event) => {
                if (event.newState === 'closed') {
                    previewIframe.src = '';
                    previewIframeMask.style.display = 'flex';
                    document.querySelector(selector.tap).style.pointerEvents = 'auto';
                }
            });

            previewIframe.addEventListener('load', () => {
                const previewContentHeader = previewIframeWindow.document.querySelector(selector.previewContentHeader);
                const previewContentMain = previewIframeWindow.document.querySelector(selector.previewContentMain);
                if (previewContentHeader && previewContentMain) {
                    previewContentHeader.style.display = 'none';
                    previewContentMain.style.marginLeft = '0';
                    previewIframeMask.style.display = 'none';
                }
            });
        },
        async insertPreviewButtonToMomentCard() {
            const previewButtonHtml = `
                <div class="moment-card__footer-button">
                    <span class="previewButton icon-button flex-center--y" data-booth-item="" data-track-prevent="click" data-booth-level="1">
                        <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" class="icon" viewBox="0 0 1024 1024">
                            <path fill="#d6d6d6" d="M935.253 494.933C849.067 294.827 686.933 170.667 512 170.667S174.933 294.827 88.747 494.933a42.667 42.667 0 0 0 0 34.134C174.933 729.173 337.067 853.333 512 853.333s337.067-124.16 423.253-324.266a42.667 42.667 0 0 0 0-34.134zM512 768c-135.253 0-263.253-97.707-337.067-256C248.747 353.707 376.747 256 512 256s263.253 97.707 337.067 256C775.253 670.293 647.253 768 512 768zm0-426.667A170.667 170.667 0 1 0 682.667 512 170.667 170.667 0 0 0 512 341.333zm0 256A85.333 85.333 0 1 1 597.333 512 85.333 85.333 0 0 1 512 597.333z"/>
                        </svg>
                    </span>
                </div>
                `;

            const [previewWrapper, previewIframe, voteButton] = await utils.getElementAndCheckExistence([selector.previewWrapper, selector.previewIframe, selector.voteButton]);
            const versionData = voteButton.dataset;
            await elmGetter.each(selector.momentCardFooter, async (momentCardFooter) => {
                const existingPreviewButton = momentCardFooter.querySelector(selector.previewButton);
                if (existingPreviewButton) existingPreviewButton.remove();
                const previewButton = utils.createElementAndInsert(previewButtonHtml, momentCardFooter, 'append');
                for (let version in versionData) {
                    if (versionData.hasOwnProperty(version)) {
                        previewButton.setAttribute(`data-${version}`, versionData[version]);
                    }
                }
                const tapElement = document.querySelector(selector.tap);
                const momentId = momentCardFooter.parentElement.dataset.eventObjKey.split(":")[1]
                const momentLink = `https://www.taptap.cn/moment/${momentId}`;
                previewButton.addEventListener('click', (event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    previewWrapper.showPopover();
                    previewIframe.src = momentLink;
                    tapElement.style.pointerEvents = 'none';
                });
            });
        },
        thePrepFunction() {
            utils.insertStyleToDocument('PreviewWrapperStyle', styles.PreviewWrapperStyle)
        },
        theMainFunction() {
            modules.thePrepFunction()
            utils.logger.info(`脚本版本|${GM_info.script.version}`)
            utils.logger.info('当前标签|已激活|开始应用配置')
            const functionsArray = [
                modules.insertPreviewElementToDocument,
                modules.insertPreviewButtonToMomentCard
            ]
            utils.executeFunctionsSequentially(functionsArray)
        }
    }
    modules.theMainFunction()
})();

QingJ © 2025

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